Sticky oder feste Navigation ist eine beliebte Designwahl, da sie Benutzern einen dauerhaften Zugriff auf die Website ermöglicht. Andererseits nimmt sie Platz auf der Seite ein und verdeckt manchmal Inhalte auf eine weniger ansprechende Weise.
Eine mögliche Lösung? Intelligente Navigation.
Definieren wir „intelligente Navigation“ als
- Sichtbar am oberen Rand der Seite
- Sichtbar, wenn der Benutzer die Seite nach **oben** bewegt (wohin auch immer er gescrollt hat)
- Versteckt, wenn der Benutzer die Seite nach **unten** bewegt
Hier ist ein Beispiel, wie das funktionieren könnte
Es ist die gesamte Bequemlichkeit einer Sticky-Positionierung mit dem zusätzlichen Vollbildvorteil. Diese Art der intelligenten Navigation ist bereits üblich (denken Sie an die URL-Leiste in vielen mobilen Browsern), ist aber manchmal ohne Bibliothek oder Plugin mühsam zu implementieren. In diesem Artikel besprechen wir daher, wie man eine mit CSS und reinem JavaScript erstellt.
Seitennotiz: Menschen haben unterschiedliche Definitionen davon, was es bedeutet, eine Seite nach unten zu scrollen (stellen Sie sich vor, wie einige Trackpad-Einstellungen die Seite nach oben scrollen, wenn Sie Ihre Finger nach unten bewegen). Für die Zwecke dieses Artikels bezieht sich das Nach-unten-Scrollen auf die Bewegung in Richtung des unteren Seitenrands.
Schauen wir uns den Code an
Hier ist etwas Beispiel-HTML. Unsere Smart-Navbar wird das <nav> sein, das über dem <main> liegt.
<nav>
<div class="logo">
Logo
</div>
<div class="links">
<a href="#">Link 1</a>
<a href="#">Link 2</a>
<a href="#">Link 3</a>
<a href="#">Link 4</a>
</div>
</nav>
<main>
<!--Place the content of your page here-->
</main>
Es ist wichtig zu beachten, dass Elemente nur relativ zu ihrem übergeordneten Container sticky sind. Der übergeordnete Container von <nav> sollte das Body-Tag sein; er sollte nicht innerhalb eines anderen Tags auf der Seite platziert werden.
Das CSS für unsere Smart-Navbar sieht so aus
nav {
position: sticky;
top: 0;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
padding: 1.5rem 2rem;
background-color: #eaeaea;
}
Nun müssen wir erkennen, wann unser Benutzer die Seite scrollt und in welche Richtung er scrollt. Ein Benutzer scrollt nach unten, wenn der Wert seiner letzten Scroll-Position kleiner ist als der Wert seiner aktuellen Scroll-Position. Wenn wir die Logik aufschlüsseln, müssen wir
- Eine Variable definieren, um die vorherige Scroll-Position zu speichern
- Eine Variable zuweisen, um die aktuelle Scroll-Position zu erkennen, die auf den Scroll-Offset der Seite gesetzt ist
Wenn die aktuelle Scroll-Position größer ist als die vorherige Scroll-Position, dann scrollt der Benutzer nach unten. Nennen wir unsere Funktion isScrollingDown
let previousScrollPosition = 0;
const isScrollingDown = () => {
let currentScrolledPosition = window.scrollY || window.pageYOffset;
let scrollingDown;
if (currentScrolledPosition > previousScrollPosition) {
scrollingDown = true;
} else {
scrollingDown = false;
}
previousScrollPosition = currentScrolledPosition;
return scrollingDown;
};
Hier ist eine visuelle Darstellung, wie diese Funktion funktioniert
Mit dieser Logik können wir erkennen, wann die Seite nach unten gescrollt wird, damit wir sie zum Umschalten unserer Navigationsstile verwenden können.
const nav = document.querySelector('nav');
const handleNavScroll = () => {
if (isScrollingDown()) {
nav.classList.add('scroll-down');
nav.classList.remove('scroll-up')
} else {
nav.classList.add('scroll-up');
nav.classList.remove('scroll-down')
}
}
Wenn der Benutzer nach unten scrollt, weisen wir eine .scroll-down Klasse zu, die unsere Styling-Methode für den Fall enthält, dass sich die Seite nach unten bewegt. Wir können unser <nav> CSS auf Folgendes aktualisieren:
nav {
/* default styling */
transition: top 500ms ease-in-out;
}
nav.scroll-up {
top: 0;
}
nav.scroll-down {
top: -100%;
}
Mit diesem Styling wird der Wert der top-Eigenschaft von <nav> auf -100% der Seitenhöhe gesetzt, sodass es aus dem Sichtbereich herausgleitet. Wir könnten uns auch dafür entscheiden, unser Styling mit translate zu handhaben oder es auszublenden – welches Animation auch immer am besten funktioniert.
Performance
Immer wenn wir mit Scroll-Event-Listenern arbeiten, sollte die Leistung sofort in den Sinn kommen. Im Moment rufen wir unsere Funktion bei jeder Seitenbewegung auf, aber wir müssen nicht jede Pixelbewegung erfassen.
In diesem Fall können wir stattdessen eine Throttle-Funktion implementieren. Eine Throttle-Funktion ist eine Funktion höherer Ordnung, die als Timer für die an sie übergebene Funktion fungiert. Wenn wir ein Scroll-Ereignis mit einem Timer von 250 ms throtteln, wird das Ereignis nur alle 250 ms aufgerufen, während der Benutzer scrollt. Dies ist eine großartige Möglichkeit, die Anzahl der Funktionsaufrufe zu begrenzen und die Leistung der Seite zu verbessern.
David Corbacho geht in diesem Artikel tiefer auf Throttle-Implementierungen ein.
Eine einfache Throttle-Implementierung in JavaScript sieht so aus
// initialize a throttleWait variable
var throttleWait;
const throttle = (callback, time) => {
// if the variable is true, don't run the function
if (throttleWait) return;
// set the wait variable to true to pause the function
throttleWait = true;
// use setTimeout to run the function within the specified time
setTimeout(() => {
callback();
// set throttleWait to false once the timer is up to restart the throttle function
throttleWait = false;
}, time);
}
Dann können wir unsere handleNavScroll Funktion in ein Throttle einbinden
window.addEventListener("scroll", () => {
throttle(handleNavScroll, 250)
});
Mit dieser Implementierung wird die handleNavScroll Funktion nur alle 250 ms einmal aufgerufen.
Barrierefreiheit (Accessibility)
Wenn wir eine benutzerdefinierte Funktion in JavaScript implementieren, müssen wir immer die Barrierefreiheit berücksichtigen. Ein solches Problem ist sicherzustellen, dass <nav> sichtbar ist, wenn es im Fokus ist. Browser neigen dazu, standardmäßig zu dem Teil der Seite zu scrollen, der gerade im Fokus ist, aber es kann bestimmte Komplikationen geben, wenn man mit Scroll-Ereignissen arbeitet.
Eine Möglichkeit, sicherzustellen, dass <nav> immer sichtbar ist, ist die Aktualisierung des CSS, um den Fokus zu berücksichtigen. Jetzt sieht unser CSS so aus:
nav.scroll-up,
nav:focus-within {
top: 0;
}
Leider ist der focus-within-Selektor nicht vollständig in allen Browsern unterstützt. Wir können einen JavaScript-Fallback dafür einfügen
const handleNavScroll = () => {
if (isScrollingDown() && !nav.contains(document.activeElement))) {
nav.classList.add('scroll-down');
nav.classList.remove('scroll-up')
} else {
nav.classList.add('scroll-up');
nav.classList.remove('scroll-down')
}
}
In dieser aktualisierten Funktion wenden wir die scroll-down Klasse nur an, wenn der Benutzer die Seite nach unten scrollt und die <nav> derzeit kein Element im Fokus hat.
Ein weiterer Aspekt der Barrierefreiheit ist die Überlegung, dass einige Benutzer möglicherweise keine Animationen auf der Seite wünschen. Das können wir mit der CSS-Media-Abfrage prefers-reduced-motion erkennen und respektieren. Wir können diese Methode in JavaScript aktualisieren und verhindern, dass unsere Funktion überhaupt ausgeführt wird, wenn ein Benutzer reduzierte Bewegung bevorzugt.
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
window.addEventListener("scroll", () => {
if (mediaQuery && !mediaQuery.matches) {
throttle(handleNavScroll, 250)
}
});
Zusammenfassung
Damit haben wir eine intelligente Navigation mit reinem CSS und reinem JavaScript implementiert. Jetzt haben die Benutzer einen dauerhaften Zugriff auf die Website, ohne Platz zu verlieren, der den Inhalt blockiert.
Darüber hinaus ist der Vorteil einer solchen benutzerdefinierten Implementierung, dass wir ein hervorragendes Benutzererlebnis erhalten, das nicht übermäßig komplex ist und keine Leistung oder Barrierefreiheit beeinträchtigt.
Für das Throttle glaube ich, dass es sinnvoller ist, die Funktion sofort auszuführen (wenn der Cooldown abgelaufen ist), als sie so zu planen, dass sie auf den Throttle-Timer wartet – das macht die Aktion reaktionsfähiger (Aktionen sollten meiner Meinung nach innerhalb von 100 ms erfolgen), da eine Wartezeit von 250–500 ms für eine Funktion, die normalerweise sofort erfolgt, träge wirkt.
Merkwürdig, das Funktionsbeispiel für die Scroll-Funktion funktioniert bei mir, aber das Beispiel für die Navigationsleiste nicht (Android, Chrome).
Meinung, die man bedenken sollte, bevor man das zu seiner Webseite hinzufügt: Als Benutzer hasse ich diese Navigationsleisten. Sie neigen immer dazu, zu erscheinen und die Teile zu verdecken, die ich auf meinem Handy lesen möchte.
Ich scrolle zu dem Teil, der mich interessiert, sodass ich meine gesamte Bildschirmfläche nutze und dort aufhöre zu scrollen – aber um dorthin zu gelangen, muss ich oft eine kleine „Aufwärtsbewegung“ machen und – zack – ist der obere Teil des sichtbaren Bereichs nicht mehr der obere Teil, weil dieses Menü den oberen Teil bedeckt. Also muss ich wieder nach unten scrollen. Menü verschwindet – also habe ich den Drang, wieder nach oben zu scrollen, um die gesamte Bildschirmfläche zu nutzen. Aber wenn ich stoppe: Menü erscheint wieder und bedeckt meine Inhalte erneut.
Aargh.
Danke! Leicht zu befolgen und hilfreich!
Ich hasse diese Art von Navigationsleisten wirklich. Es ist auch sehr unintuitiv. Man scrollt nach unten, liest etwas und möchte dann zu einer anderen Seite navigieren, aber es gibt keine Menüleiste mehr. Jetzt muss der Benutzer wissen, dass er ein wenig nach oben scrollen muss, um die Menüleiste wieder anzuzeigen.
Ich denke, es ist besser, einen visuellen Hinweis zu geben, wo sich die Menüleiste befindet. Eine gute Möglichkeit wäre, die Leiste in einen schwebenden Button zu kollabieren. So kann der Benutzer jederzeit auf den Button drücken, um die Navigationsleiste wieder zu öffnen.
Eine Lösung wäre, die Navigationsleiste anzuzeigen, sobald der Benutzer aufhört zu scrollen.
Toller Beitrag. Ich liebe eine Vanilla-JS-Lösung! ❤️
Nur ein Gedanke, aber könnte man hier die Methode
DOMTokenList.replace()verwenden?Also anstatt diesem
Man hätte dann so etwas
Vielen Dank fürs Teilen!
Weiß jemand, wie man das Problem löst, dass der Header auf Safari auf dem iPhone/iPad nicht zurückkommt? Es ist fast so, als ob er nicht merkt, dass er wieder am oberen Rand des Fensters ist.
Schön, das probiere ich für ein neues privates Projekt aus. Mal sehen.