¿Cambiar elemento de menú activo en desplazamiento de página?

95

A medida que se desplaza hacia abajo en la página, el elemento de menú activo cambia. ¿Cómo se hace esto?

Joe Bobby
fuente

Respuestas:

205

Se hace vinculando al evento de desplazamiento del contenedor (generalmente ventana).

Ejemplo rápido:

// 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");
});​

Vea lo anterior en acción en jsFiddle, incluida la animación de desplazamiento.

mekwall
fuente
2
Si su menú tiene una combinación de ID en la página y páginas normales, coloque primero los enlaces de ID en la página, luego cambie menuItems = topMenu.find("a"),a menuItems = topMenu.find("a").slice(0,4),, reemplazando 4con [sus enlaces en la página - 1].
Stephen Saucier
5
De hecho, utilicé menuItems = topMenu.find ('a [href ^ = "#"]'), por lo que solo devolvía enlaces de anclaje. Funciona de maravilla.
Julian K
1
El violín está roto. ¿Podrías arreglarlo? Thx
m1crdy
1
@ m1crdy Gracias por el aviso. Ha sido arreglado. Parece que algo en jQuery edge lo rompió. Funciona bien con 2.1.0 :)
mekwall
1
@JoelAzevedo Parece que Sizzle ha cambiado. Se actualizaron la respuesta y el caso de prueba para que funcionen con jQuery 2.2.
mekwall
16

Solo revisa mi código, francotirador y enlace de demostración:

    // 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");
            }
        });
    }

demo en vivo

MD Ashik
fuente
3

Solo para complementar la respuesta de @Marcus Ekwall. Al hacer esto, solo obtendrás enlaces de anclaje. Y no tendrá problemas si tiene una combinación de enlaces de anclaje y enlaces regulares.

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");
               }

            })
        })

Básicamente reemplacé

menuItems = topMenu.find("a"),

por

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

Para hacer coincidir todos los enlaces con el ancla en algún lugar, y cambió todo lo que era necesario para que funcione con este

Véalo en acción en jsfiddle

Pablo SG Pacheco
fuente
¿Cómo extiendo esto para un menú vertical especialmente cuando el menú es más grande que la página? pyze.com/product/docs/index.html Cuando un usuario se desplaza por el contenido a la derecha, me gustaría activar el menú apropiado a la izquierda y desplazar el menú si es necesario para mostrar el menú activo. Se agradece cualquier sugerencia.
Dickey Singh
Esto es muy Dios, gracias. Sin embargo, cambiaría el selector de atributos de * = a ^ =. Si usa * =, también verá cosas como google.com/#something aunque se trate de un enlace externo. El selector de atributos se explica bien aquí: w3schools.com/css/css_attribute_selectors.asp
Jacques
0

Si desea que la respuesta aceptada funcione en JQuery 3, cambie el código de esta manera:

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

También agregué un try-catch para evitar que javascript se bloquee si no hay ningún elemento junto a esa identificación. Siéntete libre de mejorarlo aún más;)

Tim Gerhard
fuente