Changer l'élément de menu actif sur le défilement de page?

95

Au fur et à mesure que vous faites défiler la page, l'élément de menu actif change. Comment cela se fait-il?

Joe Bobby
la source

Réponses:

205

Cela se fait en se liant à l'événement scroll du conteneur (généralement window).

Exemple rapide:

// Cache selectors
var topMenu = $("#top-menu"),
    topMenuHeight = topMenu.outerHeight()+15,
    // All list items
    menuItems = topMenu.find("a"),
    // Anchors corresponding to menu items
    scrollItems = menuItems.map(function(){
      var item = $($(this).attr("href"));
      if (item.length) { return item; }
    });

// Bind to scroll
$(window).scroll(function(){
   // Get container scroll position
   var fromTop = $(this).scrollTop()+topMenuHeight;

   // Get id of current scroll item
   var cur = scrollItems.map(function(){
     if ($(this).offset().top < fromTop)
       return this;
   });
   // Get the id of the current element
   cur = cur[cur.length-1];
   var id = cur && cur.length ? cur[0].id : "";
   // Set/remove active class
   menuItems
     .parent().removeClass("active")
     .end().filter("[href='#"+id+"']").parent().addClass("active");
});​

Voir ce qui précède en action sur jsFiddle, y compris l'animation de défilement.

mekwall
la source
2
Si votre menu est un mélange d'ID sur la page et des pages régulières, placer les liens sur la page ID, puis changer menuItems = topMenu.find("a"),pour menuItems = topMenu.find("a").slice(0,4),, en remplaçant 4par [vos liens sur la page - 1].
Stephen Saucier
5
J'ai en fait utilisé menuItems = topMenu.find ('a [href ^ = "#"]'), retournant ainsi uniquement les liens d'ancrage. Fonctionne comme un charme.
Julian K
1
Le violon est cassé. Pourriez-vous le réparer? Thx
m1crdy
1
@ m1crdy Merci pour la mise en garde. Cela a été corrigé. On dirait que quelque chose dans le bord jQuery l'a cassé. Fonctionne très bien avec 2.1.0 :)
mekwall
1
@JoelAzevedo semble que Sizzle ait changé. Mise à jour de la réponse et du scénario de test pour fonctionner avec jQuery 2.2.
mekwall
16

Vérifiez simplement mon code et mon Sniper et mon lien de démonstration:

    // Basice Code keep it 
    $(document).ready(function () {
        $(document).on("scroll", onScroll);

        //smoothscroll
        $('a[href^="#"]').on('click', function (e) {
            e.preventDefault();
            $(document).off("scroll");

            $('a').each(function () {
                $(this).removeClass('active');
            })
            $(this).addClass('active');

            var target = this.hash,
                menu = target;
            $target = $(target);
            $('html, body').stop().animate({
                'scrollTop': $target.offset().top+2
            }, 500, 'swing', function () {
                window.location.hash = target;
                $(document).on("scroll", onScroll);
            });
        });
    });

// Use Your Class or ID For Selection 

    function onScroll(event){
        var scrollPos = $(document).scrollTop();
        $('#menu-center a').each(function () {
            var currLink = $(this);
            var refElement = $(currLink.attr("href"));
            if (refElement.position().top <= scrollPos && refElement.position().top + refElement.height() > scrollPos) {
                $('#menu-center ul li a').removeClass("active");
                currLink.addClass("active");
            }
            else{
                currLink.removeClass("active");
            }
        });
    }

démo en direct

MD Ashik
la source
3

Juste pour compléter la réponse de @Marcus Ekwall. Faire comme ça n'obtiendra que des liens d'ancrage. Et vous n'allez pas avoir de problèmes si vous avez un mélange de liens d'ancrage et de liens réguliers.

jQuery(document).ready(function(jQuery) {            
            var topMenu = jQuery("#top-menu"),
                offset = 40,
                topMenuHeight = topMenu.outerHeight()+offset,
                // All list items
                menuItems =  topMenu.find('a[href*="#"]'),
                // Anchors corresponding to menu items
                scrollItems = menuItems.map(function(){
                  var href = jQuery(this).attr("href"),
                  id = href.substring(href.indexOf('#')),
                  item = jQuery(id);
                  //console.log(item)
                  if (item.length) { return item; }
                });

            // so we can get a fancy scroll animation
            menuItems.click(function(e){
              var href = jQuery(this).attr("href"),
                id = href.substring(href.indexOf('#'));
                  offsetTop = href === "#" ? 0 : jQuery(id).offset().top-topMenuHeight+1;
              jQuery('html, body').stop().animate({ 
                  scrollTop: offsetTop
              }, 300);
              e.preventDefault();
            });

            // Bind to scroll
            jQuery(window).scroll(function(){
               // Get container scroll position
               var fromTop = jQuery(this).scrollTop()+topMenuHeight;

               // Get id of current scroll item
               var cur = scrollItems.map(function(){
                 if (jQuery(this).offset().top < fromTop)
                   return this;
               });

               // Get the id of the current element
               cur = cur[cur.length-1];
               var id = cur && cur.length ? cur[0].id : "";               

               menuItems.parent().removeClass("active");
               if(id){
                    menuItems.parent().end().filter("[href*='#"+id+"']").parent().addClass("active");
               }

            })
        })

Fondamentalement, j'ai remplacé

menuItems = topMenu.find("a"),

par

menuItems =  topMenu.find('a[href*="#"]'),

Faire correspondre tous les liens avec l'ancre quelque part, et changer tout ce qui était nécessaire pour le faire fonctionner

Regardez-le en action sur jsfiddle

Pablo SG Pacheco
la source
Comment étendre cela pour un menu vertical spécialement lorsque le menu est plus grand que la page? pyze.com/product/docs/index.html Lorsqu'un utilisateur fait défiler le contenu à droite, je souhaite activer le menu approprié à gauche et faire défiler le menu si nécessaire pour afficher le menu actif. Tous les pointeurs sont appréciés.
Dickey Singh
C'est très Dieu, merci. Cependant, je changerais le sélecteur d'attribut de * = à ^ =. Si vous utilisez * = alors vous attraperiez également des choses comme google.com/#something même s'il s'agit d'un lien externe. Les sélecteurs d'attributs sont bien expliqués ici: w3schools.com/css/css_attribute_selectors.asp
Jacques
0

Si vous voulez que la réponse acceptée fonctionne dans JQuery 3, changez le code comme ceci:

var scrollItems = menuItems.map(function () {
    var id = $(this).attr("href");
    try {
        var item = $(id);
      if (item.length) {
        return item;
      }
    } catch {}
  });

J'ai également ajouté un try-catch pour empêcher javascript de planter s'il n'y a pas d'élément par cet identifiant. N'hésitez pas à l'améliorer encore plus;)

Tim Gerhard
la source