it-swarm.com.de

Markieren Sie die Menüoption, wenn Sie nach unten zum Abschnitt scrollen

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?

11
Jens Kvist

BEARBEITEN:

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.


Ursprüngliche Antwort

Anstatt .activeclass zu allen Links hinzuzufügen, sollten Sie denjenigen angeben, dessen Attribut href mit dem id der Sektion identisch ist.

Dann können Sie die .activeclass 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!


Verbessernde Leistung

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!).

Sonderfälle:

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 .

Schnipsel und Test

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!

30
David

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.

4
Johnnie

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));
0
user1020495

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?

0
duatis