Modifica location.hash senza scorrimento della pagina


148

Abbiamo alcune pagine che utilizzano Ajax per caricare i contenuti e ci sono alcune occasioni in cui è necessario un collegamento diretto a una pagina. Invece di avere un collegamento a "Utenti" e dire alle persone di fare clic su "Impostazioni" è utile essere in grado di collegare le persone alle impostazioni user.aspx #

Per consentire alle persone di fornirci i collegamenti corretti alle sezioni (per supporto tecnico, ecc.) L'ho impostato per modificare automaticamente l'hash nell'URL ogni volta che si fa clic su un pulsante. L'unico problema ovviamente è che quando ciò accade, scorre anche la pagina fino a questo elemento.

C'è un modo per disabilitarlo? Di seguito è come sto facendo questo finora.

$(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;
    });
});

Speravo che return false;ciò impedisse lo scorrimento della pagina, ma il collegamento non funzionava affatto. Quindi questo è solo un commento per ora, così posso navigare.

Qualche idea?

Risposte:


111

Passaggio 1: è necessario disattivare l'ID nodo fino a quando non è stato impostato l'hash. Questo viene fatto rimuovendo l'ID dal nodo durante l'impostazione dell'hash e quindi aggiungendolo nuovamente.

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

Passaggio 2: Alcuni browser attiveranno lo scorrimento in base alla posizione dell'ultimo nodo ID, quindi è necessario aiutarli un po '. Devi aggiungere un extra divnella parte superiore della finestra, impostare il suo ID sull'hash, quindi ripristinare tutto:

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

Passaggio 3: avvolgilo in un plug-in e usalo invece di scrivere su location.hash...


1
No, ciò che sta accadendo nel passaggio 2 è che un div nascosto viene creato e posizionato nella posizione corrente della pergamena. È visivamente uguale a position: fixed / top: 0. Pertanto, la barra di scorrimento viene "spostata" nello stesso punto esatto in cui si trova attualmente.
Borgar,

3
Questa soluzione funziona davvero bene, tuttavia la riga: top: $ .scroll (). Top + 'px' Dovrebbe essere: top: $ (finestra) .scrollTop () + 'px'
Mark Perkins

27
Sarebbe utile sapere quali browser richiedono il passaggio 2 qui.
DJ

1
Grazie Borgar. Mi confonde però che stai aggiungendo il div fx prima di eliminare l'ID del nodo di destinazione. Ciò significa che c'è un istante in cui ci sono ID duplicati nel documento. Sembra un potenziale problema, o almeno cattive maniere;)
Ben

1
Grazie mille, anche questo mi ha aiutato. Ma ho anche notato un effetto collaterale quando questo è in azione: spesso scorro bloccando il "pulsante centrale del mouse" e semplicemente spostando il mouse nella direzione in cui voglio scorrere. In Google Chrome questo interrompe il flusso di scorrimento. C'è forse una soluzione alternativa per quello?
YMMD

99

Utilizzare history.replaceStateo history.pushState* per modificare l'hash. Ciò non attiverà il salto all'elemento associato.

Esempio

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

Demo su JSFiddle

* Se si desidera la cronologia supporto avanti e indietro

Comportamento storico

Se stai utilizzando history.pushStatee non vuoi scorrere le pagine quando l'utente utilizza i pulsanti della cronologia del browser (avanti / indietro) controlla l' scrollRestorationimpostazione sperimentale (solo Chrome 46+) .

history.scrollRestoration = 'manual';

Supporto per il browser


5
replaceStateè probabilmente il modo migliore per andare qui. La differenza è l' pushStateaggiunta di un elemento alla tua cronologia mentre replaceStatenon lo fa.
Aakil Fernandes,

Grazie @AakilFernandes hai ragione. Ho appena aggiornato la risposta.
HaNdTriX,

Non è sempre quello bodyche scorre, a volte è il documentElement. Guarda questo riassunto di Diego Perini
Matijs il

1
qualche idea su ie8 / 9 qui?
user151496

1
@ user151496 check out: stackoverflow.com/revisions/...
HaNdTriX

90

Penso di aver trovato una soluzione abbastanza semplice. Il problema è che l'hash nell'URL è anche un elemento della pagina in cui si scorre. se ho appena anteposto del testo all'hash, ora non fa più riferimento a un elemento esistente!

$(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;
    });
});

Ora l'URL appare come page.aspx#btn_elementIDnon un ID reale sulla pagina. Rimuovo semplicemente "btn_" e ottengo l'ID elemento effettivo


5
Ottima soluzione Più indolore del lotto.
Swader,

Tieni presente che se sei già impegnato con URL page.aspx # elementID per qualche motivo, puoi invertire questa tecnica e anteporre "btn_" a tutti i tuoi ID
joshuahedlund,

2
Non funziona se si utilizza: target pseudo-selettori in CSS.
Jason T Featheringham,

1
Adoro questa soluzione! Stiamo usando l'hash dell'URL per la navigazione basata su AJAX, anteponendo gli ID con un /aspetto logico.
Connell,

3

Uno snippet del tuo codice originale:

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

Cambia questo in:

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

Questo non funzionerà perché l'impostazione della hashproprietà farà comunque scorrere la pagina.
jordanbtucker,

3

Di recente stavo costruendo una giostra che si basa sul window.location.hashmantenimento dello stato e ho scoperto che i browser Chrome e webkit costringono lo scorrimento (anche a una destinazione non visibile) con uno scatto imbarazzante quando l' window.onhashchangeevento viene attivato.

Anche tentando di registrare un gestore che interrompe la propagazione:

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

Non ha fatto nulla per interrompere il comportamento predefinito del browser. La soluzione che ho trovato stava usando window.history.pushStateper modificare l'hash senza innescare gli effetti collaterali indesiderati.

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

È quindi possibile ascoltare sia il normale evento hashchange sia my.hashchange:

$(window).on('hashchange my.hashchange', function(e, newUrl, oldUrl){
  // @todo - do something awesome!
});

ovviamente my.hashchangeè solo un esempio di nome evento
massimo

2

Ok, questo è un argomento piuttosto vecchio, ma ho pensato di fare il chip in quanto la risposta "corretta" non funziona bene con i CSS.

Questa soluzione sostanzialmente impedisce all'evento click di spostare la pagina in modo da poter prima ottenere la posizione di scorrimento. Quindi aggiungiamo manualmente l'hash e il browser attiva automaticamente un evento di hashchange . Catturiamo l'evento hashchange e scorriamo indietro nella posizione corretta. Un callback separa e impedisce al tuo codice di causare un ritardo mantenendo l'hacking hacking in un unico posto.

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

Non è necessario lo scambio di hash, basta scorrere immediatamente indietro
daniel.gindi

2

Ho un metodo un po 'rozzo ma sicuramente funzionante.
Basta memorizzare la posizione di scorrimento corrente in una variabile temporanea e quindi reimpostarla dopo aver modificato l'hash. :)

Quindi, per l'esempio originale:

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

Il problema con questo metodo è con i browser per dispositivi mobili che puoi notare che lo scorrimento è stato modificato
Tofandel

1

Non penso sia possibile. Per quanto ne so, l'unica volta che un browser non scorre fino a una modifica document.location.hashè se l'hash non esiste all'interno della pagina.

Questo articolo non è direttamente correlato alla tua domanda, ma discute il comportamento tipico del browser nel cambiaredocument.location.hash


1

se si utilizza l'evento hashchange con il parser hash, è possibile impedire l'azione predefinita sui collegamenti e modificare location.hash aggiungendo un carattere per fare la differenza con la proprietà id di 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);
});

Perfetto! Dopo cinque ore cercando di risolvere il problema con la ricarica dell'hash ...: / Questa è stata l'unica cosa che ha funzionato !! Grazie!!!
Igor Trindade,

1

Aggiungendo questo qui perché le domande più rilevanti sono state tutte contrassegnate come duplicati che puntano qui ...

La mia situazione è più semplice:

  • l'utente fa clic sul collegamento ( a[href='#something'])
  • il gestore di clic fa: e.preventDefault()
  • funzione smoothscroll: $("html,body").stop(true,true).animate({ "scrollTop": linkoffset.top }, scrollspeed, "swing" );
  • poi window.location = link;

In questo modo, si verifica lo scorrimento e non viene eseguito alcun salto quando la posizione viene aggiornata.


0

L'altro modo per farlo è aggiungere un div nascosto nella parte superiore della finestra. A questo div viene quindi assegnato l'id dell'hash prima che l'hash venga aggiunto all'URL .... quindi non si ottiene uno scroll.


0

Ecco la mia soluzione per le schede abilitate per la cronologia:

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

E per mostrare l'ultima scheda selezionata:

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

Nota *=sulla filterchiamata. Questa è una cosa specifica di jQuery e senza di essa le schede abilitate per la cronologia falliranno.


0

Questa soluzione crea un div nell'attuale scrollTop e lo rimuove dopo aver cambiato l'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();
});

opzionale, attivando l'evento anchor al caricamento della pagina:

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

0

Ho un metodo più semplice che funziona per me. Fondamentalmente, ricorda cos'è l'hash in HTML. È un collegamento di ancoraggio a un tag Name. Ecco perché scorre ... il browser sta tentando di scorrere fino a un collegamento di ancoraggio. Dagliene uno!

  1. Proprio sotto il tag BODY, inserisci la tua versione di questo:
 <a name="home"> </a> <a name="firstsection"> </a> <a name="secondsection"> </a> <a name="thirdsection"> </a>
  1. Assegna un nome alle div della tua sezione con le classi anziché gli ID.

  2. Nel tuo codice di elaborazione, togli il segno di hash e sostituiscilo con un punto:

    var trimPanel = loadhash.substring (1); // perde l'hash

    var dotSelect = '.' + trimPanel; // sostituisce l'hash con il punto

    $ (DotSelect) .addClass ( "activepanel") show ().; // mostra il div associato all'hash.

Infine, rimuovi element.preventDefault o return: false e consenti al nav di accadere. La finestra rimarrà in alto, l'hash verrà aggiunto all'URL della barra degli indirizzi e si aprirà il pannello corretto.


0

Penso che sia necessario ripristinare lo scorrimento nella sua posizione prima dell'hashchange.

$(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 );
    });
});

0

Se nella tua pagina usi id come una sorta di punto di ancoraggio e hai scenari in cui desideri che gli utenti aggiungano #something alla fine dell'URL e faccia scorrere la pagina fino a quella #something usando il tuo animato animato funzione javascript, il listener di eventi hashchange non sarà in grado di farlo.

Se si inserisce semplicemente un debugger immediatamente dopo l'evento hashchange, ad esempio qualcosa del genere (bene, io uso jquery, ma ottieni il punto):

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

Noterai che non appena cambi l'URL e premi il pulsante di invio, la pagina si interrompe immediatamente nella sezione corrispondente, solo dopo ciò, verrà attivata la tua funzione di scorrimento definita e scorre in quella sezione, che appare molto brutto.

Il mio consiglio è:

  1. non utilizzare id come punto di ancoraggio per la sezione che si desidera scorrere.

  2. Se devi usare l'ID, come faccio io. Usa invece il listener di eventi 'popstate', non scorrerà automaticamente fino alla stessa sezione che aggiungi all'URL, invece, puoi chiamare la tua funzione definita all'interno dell'evento popstate.

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

Infine, devi fare un piccolo trucco nella tua funzione di scorrimento definita:

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

elimina l'id sul tag e ripristinalo.

Questo dovrebbe fare il trucco.


-5

Aggiungi questo codice solo in jQuery sul documento pronto

Rif: 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;
      }
    }
  });
});
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.