Ich weiß, dass diese Frage in diesem Forum eine Million Mal gestellt wurde, aber keiner der Artikel hat mir zu einer Lösung verholfen.
Ich habe ein kleines Stück Jquery-Code erstellt, der den Hash-Link hervorhebt, wenn Sie zu dem Abschnitt mit der gleichen ID wie im Hash-Link scrollen.
$(window).scroll(function() {
var position = $(this).scrollTop();
$('.section').each(function() {
var target = $(this).offset().top;
var id = $(this).attr('id');
if (position >= target) {
$('#navigation > ul > li > a').attr('href', id).addClass('active');
}
});
});
Das Problem besteht nun darin, dass alle Hash-Links hervorgehoben werden und nicht nur der, zu dem der Abschnitt eine Beziehung hat. Kann jemand auf den Fehler hinweisen, oder habe ich etwas vergessen?
Ich habe meine Antwort geändert, um ein wenig über die Leistung und bestimmte Fälle zu sprechen.
Wenn Sie hier nur nach Code suchen, finden Sie unten ein kommentiertes Snippet.
Anstatt .active
class zu allen Links hinzuzufügen, sollten Sie denjenigen angeben, dessen Attribut href mit dem id der Sektion identisch ist.
Dann können Sie die .active
class zu diesem Link hinzufügen und aus dem Rest entfernen.
if (position >= target) {
$('#navigation > ul > li > a').removeClass('active');
$('#navigation > ul > li > a[href=#' + id + ']').addClass('active');
}
Mit der obigen Änderung wird Ihr Code den entsprechenden Link korrekt hervorheben. Ich hoffe es hilft!
Selbst wenn dieser Code seine Arbeit leistet, ist er bei weitem nicht optimal. Wie auch immer, denk dran:
Wir sollten kleine Wirkungsgrade vergessen, sagen wir 97% der Zeit: vorzeitige Optimierung ist die Wurzel allen Übels. Dennoch sollten wir nicht bestehen. erhöhen Sie unsere Chancen in diesem kritischen 3%. (Donald Knuth)
Wenn also bei einem langsamen Gerät beim Testen der Leistung keine Leistungsprobleme auftreten, können Sie das Lesen am besten beenden und über die nächste erstaunliche Funktion für Ihr Projekt nachdenken.
Grundsätzlich gibt es drei Schritte, um die Leistung zu verbessern:
Mache so viel bisherige Arbeit wie möglich:
Um zu vermeiden, dass das DOM immer wieder durchsucht wird (jedes Mal, wenn das Ereignis ausgelöst wird), können Sie Ihre jQuery-Objekte zuvor zwischenspeichern (z. B. bei document.ready
):
var $navigationLinks = $('#navigation > ul > li > a');
var $sections = $(".section");
Dann können Sie jeden Abschnitt dem entsprechenden Navigationslink zuordnen:
var sectionIdTonavigationLink = {};
$sections.each( function(){
sectionIdTonavigationLink[ $(this).attr('id') ] = $('#navigation > ul > li > a[href=\\#' + $(this).attr('id') + ']');
});
Beachten Sie die zwei umgekehrten Schrägstriche im Ankerselektor: Der Hash '#' hat eine spezielle Bedeutung in CSS, also muss maskiert werden (Danke @Johnnie ).
Sie können auch die Position jedes Abschnitts zwischenspeichern (Bootstraps Scrollspy erledigt das). Wenn Sie dies tun, müssen Sie jedoch daran denken, sie bei jeder Änderung zu aktualisieren (der Benutzer ändert das Fenster, der neue Inhalt wird über ajax hinzugefügt, ein Unterabschnitt wird erweitert usw.).
Event-Handler optimieren:
Stellen Sie sich vor, der Benutzer scrollt in einen Abschnitt: Der aktive Navigationslink muss nicht geändert werden. Wenn Sie sich jedoch den Code oben ansehen, werden Sie feststellen, dass er sich tatsächlich mehrmals ändert. Bevor der richtige Link hervorgehoben wird, werden dies auch alle vorherigen Links tun (da die entsprechenden Abschnitte auch die Bedingung position >= target
bestätigen).
Eine Lösung besteht darin, die Abschnitte von unten nach oben zu iterieren, wobei der erste Abschnitt, dessen .offset().top
gleich oder kleiner als $(window).scrollTop
ist, der richtige ist. Und ja, Sie können sich darauf verlassen, dass jQuery die Objekte in der Reihenfolge des DOMs zurückgibt (seit Version 1.3.2 ). Um von unten nach oben zu iterieren, wählen Sie sie einfach in umgekehrter Reihenfolge aus:
var $sections = $( $(".section").get().reverse() );
$sections.each( ... );
Das Doppelte $()
ist erforderlich, da get()
DOM-Elemente zurückgibt, keine jQuery-Objekte.
Wenn Sie den richtigen Abschnitt gefunden haben, sollten Sie return false
verwenden, um die Schleife zu verlassen und weitere Abschnitte nicht zu überprüfen.
Schließlich sollten Sie nichts tun, wenn der richtige Navigationslink bereits markiert ist. Überprüfen Sie es also:
if ( !$navigationLink.hasClass( 'active' ) ) {
$navigationLinks.removeClass('active');
$navigationLink.addClass('active');
}
Das Ereignis so wenig wie möglich auslösen:
Um definitiv zu verhindern, dass hochrangige Ereignisse (Scrollen, Ändern der Größe ...) Ihrer Website langsamer oder nicht mehr reagieren, besteht die Kontrolle darüber, wie oft der Event-Handler aufgerufen wird. Sie müssen nicht prüfen, welcher Link hervorgehoben werden muss 100 mal pro Sekunde! Wenn Sie neben der Link-Hervorhebung einen ausgefallenen Parallax-Effekt hinzufügen, können Sie schnelle Intro-Probleme haben.
An diesem Punkt möchten Sie sicher etwas über Drosseln, Entprellen und RequestAnimationFrame lesen. Dieser Artikel ist eine Nizza-Vorlesung und gibt einen sehr guten Überblick über drei davon. In unserem Fall entspricht das Throttling unseren Bedürfnissen.
Grundsätzlich erzwingt die Drosselung ein Mindestzeitintervall zwischen zwei Funktionsausführungen.
Ich habe eine Drosselfunktion im Snippet implementiert. Von dort aus können Sie anspruchsvoller werden, oder, noch besser, eine Bibliothek wie underscore.js oder lodash (wenn Sie nicht die gesamte Bibliothek benötigen, können Sie dort immer die Datenbank extrahieren.) Gasfunktion).
Hinweis: Wenn Sie sich umsehen, finden Sie einfachere Gasfunktionen. Hüten Sie sich vor ihnen, denn sie können den letzten Ereignisauslöser verpassen (und das ist der wichtigste!).
Ich werde diese Fälle nicht in das Snippet aufnehmen, um es nicht weiter zu komplizieren.
Im folgenden Snippet werden die Links hervorgehoben, wenn der Abschnitt ganz oben auf der Seite erscheint. Wenn Sie sie vorher hervorheben möchten, können Sie auf diese Weise einen kleinen Versatz hinzufügen:
if (position + offset >= target) {
Dies ist besonders nützlich, wenn Sie eine obere Navigationsleiste haben.
Wenn Ihr letzter Abschnitt zu klein ist, um den oberen Rand der Seite zu erreichen, können Sie den entsprechenden Link hervorheben, wenn sich die Bildlaufleiste in der untersten Position befindet:
if ( $(window).scrollTop() >= $(document).height() - $(window).height() ) {
// highlight the last link
Es gibt einige Überlegungen zur Browser-Unterstützung. Sie können mehr darüber lesen hier und hier .
Zum Schluss haben Sie hier ein kommentiertes Snippet. Bitte beachten Sie, dass ich den Namen einiger Variablen geändert habe, um sie beschreibender zu gestalten.
// cache the navigation links
var $navigationLinks = $('#navigation > ul > li > a');
// cache (in reversed order) the sections
var $sections = $($(".section").get().reverse());
// map each section id to their corresponding navigation link
var sectionIdTonavigationLink = {};
$sections.each(function() {
var id = $(this).attr('id');
sectionIdTonavigationLink[id] = $('#navigation > ul > li > a[href=\\#' + id + ']');
});
// throttle function, enforces a minimum time interval
function throttle(fn, interval) {
var lastCall, timeoutId;
return function () {
var now = new Date().getTime();
if (lastCall && now < (lastCall + interval) ) {
// if we are inside the interval we wait
clearTimeout(timeoutId);
timeoutId = setTimeout(function () {
lastCall = now;
fn.call();
}, interval - (now - lastCall) );
} else {
// otherwise, we directly call the function
lastCall = now;
fn.call();
}
};
}
function highlightNavigation() {
// get the current vertical position of the scroll bar
var scrollPosition = $(window).scrollTop();
// iterate the sections
$sections.each(function() {
var currentSection = $(this);
// get the position of the section
var sectionTop = currentSection.offset().top;
// if the user has scrolled over the top of the section
if (scrollPosition >= sectionTop) {
// get the section id
var id = currentSection.attr('id');
// get the corresponding navigation link
var $navigationLink = sectionIdTonavigationLink[id];
// if the link is not active
if (!$navigationLink.hasClass('active')) {
// remove .active class from all the links
$navigationLinks.removeClass('active');
// add .active class to the current link
$navigationLink.addClass('active');
}
// we have found our section, so we return false to exit the each loop
return false;
}
});
}
$(window).scroll( throttle(highlightNavigation,100) );
// if you don't want to throttle the function use this instead:
// $(window).scroll( highlightNavigation );
#navigation {
position: fixed;
}
#sections {
position: absolute;
left: 150px;
}
.section {
height: 200px;
margin: 10px;
padding: 10px;
border: 1px dashed black;
}
#section5 {
height: 1000px;
}
.active {
background: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="navigation">
<ul>
<li><a href="#section1">Section 1</a></li>
<li><a href="#section2">Section 2</a></li>
<li><a href="#section3">Section 3</a></li>
<li><a href="#section4">Section 4</a></li>
<li><a href="#section5">Section 5</a></li>
</ul>
</div>
<div id="sections">
<div id="section1" class="section">
I'm section 1
</div>
<div id="section2" class="section">
I'm section 2
</div>
<div id="section3" class="section">
I'm section 3
</div>
<div id="section4" class="section">
I'm section 4
</div>
<div id="section5" class="section">
I'm section 5
</div>
</div>
Und falls Sie interessiert sind, testet diese Geige die verschiedenen Verbesserungen, über die wir gesprochen haben.
Viel Spaß beim Codieren!
Für alle, die diese Lösung in letzter Zeit verwenden möchten, habe ich versucht, sie zum Laufen zu bringen. Möglicherweise müssen Sie dem Href wie folgt entkommen:
$('#navigation > ul > li > a[href=\\#' + id + ']');
Und jetzt wirft mein Browser keinen Fehler auf dieses Stück.
Ich habe Davids exzellenten Code genommen und alle jQuery-Abhängigkeiten daraus entfernt, falls sich jemand dafür interessiert:
// cache the navigation links
var $navigationLinks = document.querySelectorAll('nav > ul > li > a');
// cache (in reversed order) the sections
var $sections = document.getElementsByTagName('section');
// map each section id to their corresponding navigation link
var sectionIdTonavigationLink = {};
for (var i = $sections.length-1; i >= 0; i--) {
var id = $sections[i].id;
sectionIdTonavigationLink[id] = document.querySelectorAll('nav > ul > li > a[href=\\#' + id + ']') || null;
}
// throttle function, enforces a minimum time interval
function throttle(fn, interval) {
var lastCall, timeoutId;
return function () {
var now = new Date().getTime();
if (lastCall && now < (lastCall + interval) ) {
// if we are inside the interval we wait
clearTimeout(timeoutId);
timeoutId = setTimeout(function () {
lastCall = now;
fn.call();
}, interval - (now - lastCall) );
} else {
// otherwise, we directly call the function
lastCall = now;
fn.call();
}
};
}
function getOffset( el ) {
var _x = 0;
var _y = 0;
while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) {
_x += el.offsetLeft - el.scrollLeft;
_y += el.offsetTop - el.scrollTop;
el = el.offsetParent;
}
return { top: _y, left: _x };
}
function highlightNavigation() {
// get the current vertical position of the scroll bar
var scrollPosition = window.pageYOffset || document.documentElement.scrollTop;
// iterate the sections
for (var i = $sections.length-1; i >= 0; i--) {
var currentSection = $sections[i];
// get the position of the section
var sectionTop = getOffset(currentSection).top;
// if the user has scrolled over the top of the section
if (scrollPosition >= sectionTop - 250) {
// get the section id
var id = currentSection.id;
// get the corresponding navigation link
var $navigationLink = sectionIdTonavigationLink[id];
// if the link is not active
if (typeof $navigationLink[0] !== 'undefined') {
if (!$navigationLink[0].classList.contains('active')) {
// remove .active class from all the links
for (i = 0; i < $navigationLinks.length; i++) {
$navigationLinks[i].className = $navigationLinks[i].className.replace(/ active/, '');
}
// add .active class to the current link
$navigationLink[0].className += (' active');
}
} else {
// remove .active class from all the links
for (i = 0; i < $navigationLinks.length; i++) {
$navigationLinks[i].className = $navigationLinks[i].className.replace(/ active/, '');
}
}
// we have found our section, so we return false to exit the each loop
return false;
}
}
}
window.addEventListener('scroll',throttle(highlightNavigation,150));
In dieser Zeile:
$('#navigation > ul > li > a').attr('href', id).addClass('active');
Sie setzen tatsächlich das href-Attribut jedes $ ('# navigation> ul> li> a') - Elements und fügen dann die aktive Klasse allen hinzu. Möglicherweise müssen Sie Folgendes tun:
$('#navigation > ul > li > a[href=#' + id + ']')
Und wähle nur das aus, welches mit der ID übereinstimmt. Sinn ergeben?