Modificando location.hash sin desplazamiento de página

148

Tenemos algunas páginas que usan ajax para cargar contenido y hay algunas ocasiones en las que necesitamos un enlace profundo en una página. En lugar de tener un enlace a "Usuarios" y decirle a la gente que haga clic en "configuración", es útil poder vincular personas a la configuración de user.aspx #

Para permitir que las personas nos brinden enlaces correctos a las secciones (para soporte técnico, etc.), lo configuré para modificar automáticamente el hash en la URL cada vez que se hace clic en un botón. El único problema, por supuesto, es que cuando esto sucede, también desplaza la página a este elemento.

¿Hay alguna manera de desactivar esto? A continuación se muestra cómo lo estoy haciendo hasta ahora.

$(function(){
    //This emulates a click on the correct button on page load
    if(document.location.hash){
     $("#buttons li a").removeClass('selected');
     s=$(document.location.hash).addClass('selected').attr("href").replace("javascript:","");
     eval(s);
    }

    //Click a button to change the hash
    $("#buttons li a").click(function(){
            $("#buttons li a").removeClass('selected');
            $(this).addClass('selected');
            document.location.hash=$(this).attr("id")
            //return false;
    });
});

Esperaba return false;que detuviera el desplazamiento de la página, pero solo hace que el enlace no funcione en absoluto. Eso es solo comentado por ahora para que pueda navegar.

¿Algunas ideas?

CBarr
fuente

Respuestas:

111

Paso 1: debe desactivar la ID del nodo, hasta que se haya configurado el hash. Esto se hace quitando la ID del nodo mientras se configura el hash y luego volviéndolo a agregar.

hash = hash.replace( /^#/, '' );
var node = $( '#' + hash );
if ( node.length ) {
  node.attr( 'id', '' );
}
document.location.hash = hash;
if ( node.length ) {
  node.attr( 'id', hash );
}

Paso 2: Algunos navegadores activarán el desplazamiento en función de dónde se vio por última vez el nodo ID, por lo que debe ayudarlos un poco. Debe agregar un extra diva la parte superior de la ventana gráfica, establecer su ID en el hash y luego deshacer todo:

hash = hash.replace( /^#/, '' );
var fx, node = $( '#' + hash );
if ( node.length ) {
  node.attr( 'id', '' );
  fx = $( '<div></div>' )
          .css({
              position:'absolute',
              visibility:'hidden',
              top: $(document).scrollTop() + 'px'
          })
          .attr( 'id', hash )
          .appendTo( document.body );
}
document.location.hash = hash;
if ( node.length ) {
  fx.remove();
  node.attr( 'id', hash );
}

Paso 3: envuélvelo en un complemento y úsalo en lugar de escribir en location.hash...

Borgar
fuente
1
No, lo que sucede en el paso 2 es que se crea un div oculto y se coloca en la ubicación actual del desplazamiento. Es lo mismo visualmente como posición: fijo / superior: 0. Por lo tanto, la barra de desplazamiento se "mueve" exactamente al mismo lugar en el que se encuentra actualmente.
Borgar
3
Esta solución funciona bien, sin embargo, la línea: top: $ .scroll (). Top + 'px' debería ser: top: $ (window) .scrollTop () + 'px'
Mark Perkins
27
Sería útil saber qué navegadores necesitan el paso 2 aquí.
djc
1
Gracias borgar Sin embargo, me sorprende que esté agregando el div fx antes de eliminar la identificación del nodo de destino. Eso significa que hay un instante en el que hay identificaciones duplicadas en el documento. Parece un problema potencial, o al menos malos modales;)
Ben
1
Muchas gracias, esto también me ayudó. Pero también noté un efecto secundario cuando esto está en acción: a menudo me desplazo bloqueando el "botón central del mouse" y simplemente moviendo el mouse en la dirección que quiero desplazar. En Google Chrome esto interrumpe el flujo de desplazamiento. ¿Tal vez hay una solución elegante para eso?
YMMD
99

Use history.replaceStateo history.pushState* para cambiar el hash. Esto no activará el salto al elemento asociado.

Ejemplo

$(document).on('click', 'a[href^=#]', function(event) {
  event.preventDefault();
  history.pushState({}, '', this.href);
});

Demo en JSFiddle

* Si desea historial de soporte hacia adelante y hacia atrás

Comportamiento de la historia

Si está utilizando history.pushStatey no desea desplazarse por la página cuando el usuario usa los botones del historial del navegador (hacia adelante / hacia atrás), consulte la scrollRestorationconfiguración experimental (solo Chrome 46+) .

history.scrollRestoration = 'manual';

Soporte de navegador

HaNdTriX
fuente
55
replaceStateEs probablemente la mejor manera de ir aquí. La diferencia es que pushStateagrega un elemento a su historial mientras replaceStateque no lo hace.
Aakil Fernandes
Gracias @AakilFernandes tienes razón. Acabo de actualizar la respuesta.
HaNdTriX
No siempre es el bodyque se desplaza, a veces es el documentElement. Ver esta esencia de Diego Perini
Matijs
1
alguna idea sobre ie8 / 9 aquí?
user151496
1
@ user151496 echa un vistazo a: stackoverflow.com/revisions/…
HaNdTriX
90

Creo que pude haber encontrado una solución bastante simple. El problema es que el hash en la URL también es un elemento en la página a la que se desplaza. si solo antepongo algo de texto al hash, ahora ya no hace referencia a un elemento existente.

$(function(){
    //This emulates a click on the correct button on page load
    if(document.location.hash){
     $("#buttons li a").removeClass('selected');
     s=$(document.location.hash.replace("btn_","")).addClass('selected').attr("href").replace("javascript:","");
     eval(s);
    }

    //Click a button to change the hash
    $("#buttons li a").click(function(){
            $("#buttons li a").removeClass('selected');
            $(this).addClass('selected');
            document.location.hash="btn_"+$(this).attr("id")
            //return false;
    });
});

Ahora aparece la URL page.aspx#btn_elementIDque no es una ID real en la página. Simplemente elimino "btn_" y obtengo el ID del elemento real

CBarr
fuente
55
Gran solución Lo más indoloro del lote.
Swader
Tenga en cuenta que si ya está comprometido con Page.aspx # elementID URL por alguna razón se puede revertir esta técnica y anteponer "btn_" a todas sus identificaciones
joshuahedlund
2
No funciona si está utilizando: pseudo-selectores de destino en CSS.
Jason T Featheringham
1
Me encanta esta solución! Estamos utilizando el hash de URL para la navegación basada en AJAX, anteponiendo las ID con un /aspecto lógico.
Connell
3

Un fragmento de su código original:

$("#buttons li a").click(function(){
    $("#buttons li a").removeClass('selected');
    $(this).addClass('selected');
    document.location.hash=$(this).attr("id")
});

Cambia esto a:

$("#buttons li a").click(function(e){
    // need to pass in "e", which is the actual click event
    e.preventDefault();
    // the preventDefault() function ... prevents the default action.
    $("#buttons li a").removeClass('selected');
    $(this).addClass('selected');
    document.location.hash=$(this).attr("id")
});
Daniel
fuente
Esto no funcionará porque establecer la hashpropiedad hará que la página se desplace de todos modos.
jordanbtucker
3

Recientemente construí un carrusel que se basa en window.location.hashmantener el estado e hice el descubrimiento de que los navegadores Chrome y webkit forzarán el desplazamiento (incluso a un objetivo no visible) con un tirón incómodo cuando window.onhashchangese dispara el evento.

Incluso intentando registrar un controlador que detiene la propagación:

$(window).on("hashchange", function(e) { 
  e.stopPropogation(); 
  e.preventDefault(); 
});

No hizo nada para detener el comportamiento predeterminado del navegador. La solución que encontré fue usar window.history.pushStatepara cambiar el hash sin desencadenar los efectos secundarios indeseables.

 $("#buttons li a").click(function(){
    var $self, id, oldUrl;

    $self = $(this);
    id = $self.attr('id');

    $self.siblings().removeClass('selected'); // Don't re-query the DOM!
    $self.addClass('selected');

    if (window.history.pushState) {
      oldUrl = window.location.toString(); 
      // Update the address bar 
      window.history.pushState({}, '', '#' + id);
      // Trigger a custom event which mimics hashchange
      $(window).trigger('my.hashchange', [window.location.toString(), oldUrl]);
    } else {
      // Fallback for the poors browsers which do not have pushState
      window.location.hash = id;
    }

    // prevents the default action of clicking on a link.
    return false;
});

Luego puede escuchar tanto el evento de cambio de hash normal como my.hashchange:

$(window).on('hashchange my.hashchange', function(e, newUrl, oldUrl){
  // @todo - do something awesome!
});
max
fuente
por supuesto, my.hashchangees solo un ejemplo de nombre de evento
máximo
2

De acuerdo, este es un tema bastante antiguo, pero pensé en incorporarlo ya que la respuesta 'correcta' no funciona bien con CSS.

Esta solución básicamente evita que el evento de clic mueva la página para que podamos obtener primero la posición de desplazamiento. Luego agregamos manualmente el hash y el navegador activa automáticamente un evento de hashchange . Capturamos el evento hashchange y volvemos a la posición correcta. Una devolución de llamada separa e impide que su código provoque un retraso al mantener su hackeo en un solo lugar.

var hashThis = function( $elem, callback ){
    var scrollLocation;
    $( $elem ).on( "click", function( event ){
        event.preventDefault();
        scrollLocation = $( window ).scrollTop();
        window.location.hash = $( event.target ).attr('href').substr(1);
    });
    $( window ).on( "hashchange", function( event ){
        $( window ).scrollTop( scrollLocation );
        if( typeof callback === "function" ){
            callback();
        }
    });
}
hashThis( $( ".myAnchor" ), function(){
    // do something useful!
});
Egor Kloos
fuente
No necesita el cambio de hash, solo desplácese hacia atrás inmediatamente
daniel.gindi
2

Erm, tengo un método algo tosco pero definitivamente de trabajo.
Simplemente almacene la posición de desplazamiento actual en una variable temporal y luego reiníciela después de cambiar el hash. :)

Entonces, para el ejemplo original:

$("#buttons li a").click(function(){
        $("#buttons li a").removeClass('selected');
        $(this).addClass('selected');

        var scrollPos = $(document).scrollTop();
        document.location.hash=$(this).attr("id")
        $(document).scrollTop(scrollPos);
});
Jan Paepke
fuente
El problema con este método es que con los navegadores móviles puede notar que se modifica el desplazamiento
Tofandel
1

No creo que esto sea posible. Hasta donde sé, la única vez que un navegador no se desplaza a un cambio document.location.hashes si el hash no existe dentro de la página.

Este artículo no está directamente relacionado con su pregunta, pero analiza el comportamiento típico del navegador al cambiardocument.location.hash

Ben S
fuente
1

si usa el evento hashchange con el analizador hash, puede evitar la acción predeterminada en los enlaces y cambiar la ubicación. Hash agrega un carácter para tener una diferencia con la propiedad id de un elemento

$('a[href^=#]').on('click', function(e){
    e.preventDefault();
    location.hash = $(this).attr('href')+'/';
});

$(window).on('hashchange', function(){
    var a = /^#?chapter(\d+)-section(\d+)\/?$/i.exec(location.hash);
});
polkila
fuente
¡Perfecto! Después de cinco horas tratando de solucionar el problema con la recarga de hash ...: / ¡¡¡Esto fue lo único que funcionó !! ¡¡¡Gracias!!!
Igor Trindade
1

Agregando esto aquí porque las preguntas más relevantes se han marcado como duplicados apuntando aquí ...

Mi situación es más simple:

  • el usuario hace clic en el enlace ( a[href='#something'])
  • el controlador de clics hace: e.preventDefault()
  • función de desplazamiento suave: $("html,body").stop(true,true).animate({ "scrollTop": linkoffset.top }, scrollspeed, "swing" );
  • luego window.location = link;

De esta manera, se produce el desplazamiento y no hay salto cuando se actualiza la ubicación.

ptim
fuente
0

La otra forma de hacerlo es agregar un div que esté oculto en la parte superior de la ventana gráfica. A este div se le asigna la identificación del hash antes de que se agregue el hash a la url ... para que no obtenga un desplazamiento.

Mike Rifgin
fuente
0

Aquí está mi solución para pestañas con historial habilitado:

    var tabContainer = $(".tabs"),
        tabsContent = tabContainer.find(".tabsection").hide(),
        tabNav = $(".tab-nav"), tabs = tabNav.find("a").on("click", function (e) {
                e.preventDefault();
                var href = this.href.split("#")[1]; //mydiv
                var target = "#" + href; //#myDiv
                tabs.each(function() {
                    $(this)[0].className = ""; //reset class names
                });
                tabsContent.hide();
                $(this).addClass("active");
                var $target = $(target).show();
                if ($target.length === 0) {
                    console.log("Could not find associated tab content for " + target);
                } 
                $target.removeAttr("id");
                // TODO: You could add smooth scroll to element
                document.location.hash = target;
                $target.attr("id", href);
                return false;
            });

Y para mostrar la última pestaña seleccionada:

var currentHashURL = document.location.hash;
        if (currentHashURL != "") { //a tab was set in hash earlier
            // show selected
            $(currentHashURL).show();
        }
        else { //default to show first tab
            tabsContent.first().show();
        }
        // Now set the tab to active
        tabs.filter("[href*='" + currentHashURL + "']").addClass("active");

Tenga *=en cuenta el en la filterllamada. Esto es algo específico de jQuery, y sin él, sus pestañas habilitadas para el historial fallarán.

usuario1429980
fuente
0

Esta solución crea un div en el scrollTop real y lo elimina después de cambiar el hash:

$('#menu a').on('click',function(){
    //your anchor event here
    var href = $(this).attr('href');
    window.location.hash = href;
    if(window.location.hash == href)return false;           
    var $jumpTo = $('body').find(href);
    $('body').append(
        $('<div>')
            .attr('id',$jumpTo.attr('id'))
            .addClass('fakeDivForHash')
            .data('realElementForHash',$jumpTo.removeAttr('id'))
            .css({'position':'absolute','top':$(window).scrollTop()})
    );
    window.location.hash = href;    
});
$(window).on('hashchange', function(){
    var $fakeDiv = $('.fakeDivForHash');
    if(!$fakeDiv.length)return true;
    $fakeDiv.data('realElementForHash').attr('id',$fakeDiv.attr('id'));
    $fakeDiv.remove();
});

opcional, desencadenando evento de anclaje en la carga de la página:

$('#menu a[href='+window.location.hash+']').click();

fuente
0

Tengo un método más simple que funciona para mí. Básicamente, recuerda cuál es el hash en HTML. Es un enlace de anclaje a una etiqueta de Nombre. Es por eso que se desplaza ... el navegador está intentando desplazarse a un enlace de anclaje. Entonces, ¡dale uno!

  1. Justo debajo de la etiqueta BODY, ponga su versión de esto:
 <a name="home"> </a> <a name="firstsection"> </a> <a name="secondsection"> </a> <a name="thirdsection"> </a>
  1. Nombra tus divs de sección con clases en lugar de ID.

  2. En su código de procesamiento, elimine la marca hash y reemplácela con un punto:

    var trimPanel = loadhash.substring (1); // pierde el hash

    var dotSelect = '.' + trimPanel; // reemplaza hash con punto

    $ (dotSelect) .addClass ("activepanel"). show (); // muestra el div asociado con el hash.

Finalmente, elimine element.preventDefault o return: false y permita que ocurra la navegación. La ventana permanecerá en la parte superior, el hash se agregará a la URL de la barra de direcciones y se abrirá el panel correcto.

ColdSharper
fuente
0

Creo que debe restablecer el desplazamiento a su posición antes del cambio de hash.

$(function(){
    //This emulates a click on the correct button on page load
    if(document.location.hash) {
        $("#buttons li a").removeClass('selected');
        s=$(document.location.hash).addClass('selected').attr("href").replace("javascript:","");
        eval(s);
    }

    //Click a button to change the hash
    $("#buttons li a").click(function() {
            var scrollLocation = $(window).scrollTop();
            $("#buttons li a").removeClass('selected');
            $(this).addClass('selected');
            document.location.hash = $(this).attr("id");
            $(window).scrollTop( scrollLocation );
    });
});
iProDev
fuente
0

Si en su página usa la identificación como una especie de punto de anclaje, y tiene escenarios en los que desea que los usuarios agreguen #algo al final de la URL y que la página se desplace a esa sección #algo usando su propia animación animada definida función javascript, el detector de eventos hashchange no podrá hacer eso.

Si simplemente coloca un depurador inmediatamente después del evento hashchange, por ejemplo, algo como esto (bueno, uso jquery, pero entiende el punto):

$(window).on('hashchange', function(){debugger});

Notarás que tan pronto como cambies tu URL y presiones el botón Intro, la página se detendrá en la sección correspondiente inmediatamente, solo después de eso, se activará tu propia función de desplazamiento definida, y se desplazará a esa sección, que se ve muy mal.

Mi sugerencia es:

  1. no use id como punto de anclaje a la sección a la que desea desplazarse.

  2. Si debe usar una identificación, como lo hago yo. Utilice el detector de eventos 'popstate' en su lugar, no se desplazará automáticamente a la sección que agrega a la url, en su lugar, puede llamar a su propia función definida dentro del evento popstate.

    $(window).on('popstate', function(){myscrollfunction()});

Finalmente necesita hacer un pequeño truco en su propia función de desplazamiento definida:

    let hash = window.location.hash.replace(/^#/, '');
    let node = $('#' + hash);
    if (node.length) {
        node.attr('id', '');
    }
    if (node.length) {
        node.attr('id', hash);
    }

elimine la identificación de su etiqueta y reiníciela.

Esto debería funcionar.

Shuwei
fuente
-5

Solo agregue este código a jQuery en el documento listo

Ref: http://css-tricks.com/snippets/jquery/smooth-scrolling/

$(function() {
  $('a[href*=#]:not([href=#])').click(function() {
    if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'') && location.hostname == this.hostname) {
      var target = $(this.hash);
      target = target.length ? target : $('[name=' + this.hash.slice(1) +']');
      if (target.length) {
        $('html,body').animate({
          scrollTop: target.offset().top
        }, 1000);
        return false;
      }
    }
  });
});
Saygın Karahan
fuente