Ändern von location.hash ohne Bildlauf

147

Wir haben einige Seiten, auf denen Ajax zum Laden von Inhalten verwendet wird, und es gibt einige Fälle, in denen wir einen Deep Link zu einer Seite erstellen müssen. Anstatt einen Link zu "Benutzer" zu haben und den Leuten zu sagen, dass sie auf "Einstellungen" klicken sollen, ist es hilfreich, Leute zu verlinken anzuweisen den Einstellungen user.aspx # können

Damit die Leute uns korrekte Links zu Abschnitten (für technischen Support usw.) zur Verfügung stellen können, habe ich es so eingerichtet, dass der Hash in der URL automatisch geändert wird, wenn auf eine Schaltfläche geklickt wird. Das einzige Problem ist natürlich, dass in diesem Fall auch die Seite zu diesem Element gescrollt wird.

Gibt es eine Möglichkeit, dies zu deaktivieren? Unten ist, wie ich das bisher mache.

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

Ich hatte gehofft, das return false;würde das Scrollen der Seite verhindern - aber dadurch funktioniert der Link überhaupt nicht. Das ist also erst einmal auskommentiert, damit ich navigieren kann.

Irgendwelche Ideen?

CBarr
quelle

Antworten:

111

Schritt 1: Sie müssen die Knoten-ID entschärfen, bis der Hash festgelegt wurde. Dazu wird die ID vom Knoten entfernt, während der Hash festgelegt wird, und anschließend wieder hinzugefügt.

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

Schritt 2: Einige Browser lösen den Bildlauf basierend darauf aus, wo der ID-Knoten zuletzt gesehen wurde. Sie müssen ihnen also ein wenig helfen. Sie müssen ein zusätzliches hinzufügendiv oben im Ansichtsfenster , die ID auf den Hash setzen und dann alles zurücksetzen:

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

Schritt 3: Wickeln Sie es in ein Plugin ein und verwenden Sie es, anstatt location.hash...

Borgar
quelle
1
Nein, in Schritt 2 wird ein verstecktes Div erstellt und an der aktuellen Position der Schriftrolle platziert. Es ist optisch dasselbe wie Position: fest / oben: 0. Somit wird die Bildlaufleiste genau an die Stelle "verschoben", an der sie sich gerade befindet.
Borgar
3
Diese Lösung funktioniert in der Tat gut - aber die Zeile: top: $ .scroll (). Top + 'px' sollte sein: top: $ (window) .scrollTop () + 'px'
Mark Perkins
27
Es wäre hilfreich zu wissen, welche Browser hier Schritt 2 benötigen.
DJC
1
Danke Borgar. Es ist mir allerdings ein Rätsel, dass Sie das fx div anhängen, bevor Sie die ID des Zielknotens löschen. Das heißt, es gibt einen Moment, in dem das Dokument doppelte IDs enthält. Scheint ein potenzielles Problem oder zumindest schlechte Manieren zu sein;)
Ben
1
Vielen Dank, das hat mir auch geholfen. Aber ich habe auch einen Nebeneffekt bemerkt, wenn dies in Aktion ist: Ich scrolle oft, indem ich die "mittlere Maustaste" sperre und die Maus einfach in die Richtung bewege, in die ich scrollen möchte. In Google Chrome unterbricht dies den Bildlauffluss. Gibt es vielleicht eine ausgefallene Problemumgehung dafür?
YMMD
99

Verwenden Sie history.replaceStateoder history.pushState*, um den Hash zu ändern. Dadurch wird der Sprung zum zugehörigen Element nicht ausgelöst.

Beispiel

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

Demo auf JSFiddle

* Wenn Sie Unterstützung für den Verlauf vorwärts und rückwärts wünschen

Verlaufsverhalten

Wenn Sie verwenden history.pushStateund keine Seiten scrollen möchten, wenn der Benutzer die Verlaufsschaltflächen des Browsers verwendet (vorwärts / rückwärts), überprüfen Sie die experimentelle scrollRestorationEinstellung (nur Chrome 46+) .

history.scrollRestoration = 'manual';

Browser-Unterstützung

HaNdTriX
quelle
5
replaceStateist wahrscheinlich der bessere Weg hierher zu gehen. Der Unterschied besteht darin pushState, dass Sie Ihrem Verlauf ein Element hinzufügen, während replaceStatedies nicht der Fall ist.
Aakil Fernandes
Danke @AakilFernandes du hast recht. Ich habe gerade die Antwort aktualisiert.
HaNdTriX
Es ist nicht immer das body, was rollt, manchmal ist es das documentElement. Sehen Sie diesen Kern von Diego Perini
Matijs
1
eine idee zu ie8 / 9 hier?
user151496
1
@ user151496 check out: stackoverflow.com/revisions/…
HaNdTriX
90

Ich glaube, ich habe eine ziemlich einfache Lösung gefunden. Das Problem ist, dass der Hash in der URL auch ein Element auf der Seite ist, zu der Sie einen Bildlauf durchführen. Wenn ich dem Hash nur einen Text voranstelle, verweist er jetzt nicht mehr auf ein vorhandenes Element!

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

Jetzt wird die URL als page.aspx#btn_elementIDkeine echte ID auf der Seite angezeigt . Ich entferne einfach "btn_" und erhalte die tatsächliche Element-ID

CBarr
quelle
5
Tolle Lösung. Am schmerzlosesten.
Swader
Beachten Sie, dass Sie, wenn Sie sich aus irgendeinem Grund bereits für page.aspx # elementID-URLs entschieden haben, diese Technik umkehren und "btn_" allen Ihren IDs voranstellen können
joshuahedlund
2
Funktioniert nicht, wenn Sie Folgendes verwenden: Ziel-Pseudo-Selektoren in CSS.
Jason T Featheringham
1
Ich liebe diese Lösung! Wir verwenden den URL-Hash für die AJAX-basierte Navigation und stellen den IDs ein /logisches Zeichen voran .
Connell
3

Ein Ausschnitt aus Ihrem Originalcode:

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

Ändern Sie dies 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")
});
Daniel
quelle
Dies funktioniert nicht, da durch das Festlegen der hashEigenschaft die Seite ohnehin gescrollt wird.
Jordanbtucker
3

Ich habe kürzlich ein Karussell gebaut, das window.location.hashden Status beibehält, und festgestellt, dass Chrome- und Webkit-Browser das Scrollen (sogar zu einem nicht sichtbaren Ziel) mit einem unangenehmen Ruck erzwingen, wenn das window.onhashchangeEreignis ausgelöst wird.

Sogar der Versuch, einen Handler zu registrieren, der die Propagierung stoppt:

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

Es wurde nichts unternommen, um das Standardverhalten des Browsers zu stoppen. Die Lösung, die ich gefunden habe, bestand window.history.pushStatedarin, den Hash zu ändern, ohne die unerwünschten Nebenwirkungen auszulösen.

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

Sie können dann sowohl auf das normale Hashchange-Ereignis als auch auf Folgendes warten my.hashchange:

$(window).on('hashchange my.hashchange', function(e, newUrl, oldUrl){
  // @todo - do something awesome!
});
max
quelle
Natürlich my.hashchangeist nur ein Beispiel Ereignisname
max
2

Okay, das ist ein ziemlich altes Thema, aber ich dachte, ich würde mich einmischen, da die 'richtige' Antwort mit CSS nicht gut funktioniert.

Diese Lösung verhindert grundsätzlich, dass das Klickereignis die Seite verschiebt, sodass wir zuerst die Bildlaufposition ermitteln können. Dann fügen wir den Hash manuell hinzu und der Browser löst automatisch ein Hashchange- Ereignis aus. Wir erfassen das Hashchange-Ereignis und scrollen zurück zur richtigen Position. Ein Rückruf trennt und verhindert, dass Ihr Code eine Verzögerung verursacht, indem Ihr Hash-Hacking an einem Ort bleibt.

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
quelle
Sie brauchen den Hash-Wechsel nicht, scrollen Sie einfach sofort zurück
daniel.gindi
2

Ähm, ich habe eine etwas grobe, aber definitiv funktionierende Methode.
Speichern Sie einfach die aktuelle Bildlaufposition in einer temporären Variablen und setzen Sie sie nach dem Ändern des Hash zurück. :) :)

Also für das ursprüngliche Beispiel:

$("#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
quelle
Das Problem mit dieser Methode ist, dass Sie bei mobilen
Browsern
1

Ich denke nicht, dass das möglich ist. Soweit ich weiß, blättert ein Browser nur dann nicht zu einer Änderungdocument.location.hash wenn der Hash nicht auf der Seite vorhanden ist.

Dieser Artikel steht nicht in direktem Zusammenhang mit Ihrer Frage, beschreibt jedoch das typische Browserverhalten beim Änderndocument.location.hash

Ben S.
quelle
1

Wenn Sie das Hashchange-Ereignis mit dem Hash-Parser verwenden, können Sie die Standardaktion für Links verhindern und location.hash ändern, indem Sie ein Zeichen hinzufügen, um einen Unterschied zur id-Eigenschaft eines Elements zu erzielen

$('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
quelle
Perfekt! Nach fünf Stunden Versuch, das Problem mit dem Hash-Reload zu beheben ...: / Dies war das einzige, was funktionierte !! Vielen Dank!!!
Igor Trindade
1

Fügen Sie dies hier hinzu, da die relevanteren Fragen alle als Duplikate markiert wurden, die hier zeigen…

Meine Situation ist einfacher:

  • Benutzer klickt auf den Link ( a[href='#something'])
  • Klick-Handler macht: e.preventDefault()
  • Smoothscroll-Funktion: $("html,body").stop(true,true).animate({ "scrollTop": linkoffset.top }, scrollspeed, "swing" );
  • dann window.location = link;

Auf diese Weise erfolgt der Bildlauf, und es wird kein Sprung ausgeführt, wenn der Speicherort aktualisiert wird.

ptim
quelle
0

Die andere Möglichkeit besteht darin, ein Div hinzuzufügen, das oben im Ansichtsfenster ausgeblendet ist. Diesem Div wird dann die ID des Hashs zugewiesen, bevor der Hash zur URL hinzugefügt wird. Dann erhalten Sie keine Schriftrolle.

Mike Rifgin
quelle
0

Hier ist meine Lösung für geschichtsaktivierte Registerkarten:

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

Und um die zuletzt ausgewählte Registerkarte anzuzeigen:

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

Beachten Sie die *=auf dem filterAnruf. Dies ist eine jQuery-spezifische Sache, und ohne sie schlagen Ihre geschichtsaktivierten Registerkarten fehl.

user1429980
quelle
0

Diese Lösung erstellt ein div am eigentlichen scrollTop und entfernt es nach dem Ändern des Hashs:

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

optional, Ankerereignis beim Laden der Seite auslösen:

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

quelle
0

Ich habe eine einfachere Methode, die für mich funktioniert. Denken Sie grundsätzlich daran, was der Hash tatsächlich in HTML ist. Es ist ein Ankerlink zu einem Namensschild. Deshalb wird gescrollt ... der Browser versucht, zu einem Ankerlink zu scrollen. Also, gib es eins!

  1. Fügen Sie direkt unter dem BODY-Tag Ihre Version davon ein:
 <a name="home"> </a> <a name="firstsection"> </a> <a name="secondsection"> </a> <a name="thirdsection"> </a>
  1. Benennen Sie Ihre Abschnittsdivs mit Klassen anstelle von IDs.

  2. Entfernen Sie in Ihrem Verarbeitungscode die Raute und ersetzen Sie sie durch einen Punkt:

    var trimPanel = loadhash.substring (1); // den Hash verlieren

    var dotSelect = '.' + trimPanel; // Hash durch Punkt ersetzen

    $ (dotSelect) .addClass ("activepanel"). show (); // zeige das mit dem Hash verknüpfte div.

Entfernen Sie schließlich element.preventDefault oder geben Sie: false zurück und lassen Sie die Navigation zu. Das Fenster bleibt oben, der Hash wird an die URL der Adressleiste angehängt und das richtige Fenster wird geöffnet.

ColdSharper
quelle
0

Ich denke, Sie müssen den Bildlauf vor dem Hash-Wechsel auf seine Position zurücksetzen.

$(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
quelle
0

Wenn Sie auf Ihrer Seite die ID als eine Art Ankerpunkt verwenden und Szenarien haben, in denen Benutzer #something an das Ende der URL anhängen und die Seite mithilfe Ihrer eigenen definierten Animation zu diesem #something-Abschnitt scrollen sollen Javascript-Funktion, Hashchange-Ereignis-Listener kann das nicht.

Wenn Sie einfach einen Debugger unmittelbar nach dem Hashchange-Ereignis einfügen, zum Beispiel so etwas (nun, ich verwende jquery, aber Sie verstehen es):

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

Sie werden feststellen, dass die Seite sofort nach dem Ändern Ihrer URL und dem Drücken der Eingabetaste an der entsprechenden Stelle stoppt. Danach wird Ihre eigene definierte Bildlauffunktion ausgelöst und es wird eine Art Bildlauf zu diesem Abschnitt durchgeführt, der aussieht sehr schlecht.

Mein Vorschlag ist:

  1. Verwenden Sie id nicht als Ankerpunkt für den Abschnitt, zu dem Sie scrollen möchten.

  2. Wenn Sie ID verwenden müssen, wie ich. Verwenden Sie stattdessen den Ereignis-Listener 'popstate'. Es wird nicht automatisch zu dem Abschnitt gescrollt, den Sie an die URL anhängen. Stattdessen können Sie Ihre eigene definierte Funktion innerhalb des popstate-Ereignisses aufrufen.

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

Schließlich müssen Sie einen kleinen Trick in Ihrer eigenen definierten Bildlauffunktion ausführen:

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

Löschen Sie die ID Ihres Tags und setzen Sie sie zurück.

Dies sollte den Trick tun.

Shuwei
quelle
-5

Fügen Sie diesen Code nur dann in jQuery ein, wenn das Dokument fertig ist

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
quelle