Modyfikowanie location.hash bez przewijania strony

148

Mamy kilka stron korzystających z technologii AJAX do wczytywania zawartości i jest kilka sytuacji, w których potrzebujemy głębokiego linku do strony. Zamiast mieć link do „Użytkownicy” i mówić ludziom, aby kliknęli „ustawienia”, warto mieć możliwość powiązania ludzi z user.aspx # settings

Aby umożliwić ludziom dostarczanie nam poprawnych linków do sekcji (w celu uzyskania pomocy technicznej itp.), Skonfigurowałem automatyczne modyfikowanie skrótu w adresie URL po każdym kliknięciu przycisku. Jedynym problemem jest oczywiście to, że kiedy tak się dzieje, przewija się również stronę do tego elementu.

Czy jest sposób, aby to wyłączyć? Poniżej znajduje się, jak dotychczas to robię.

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

Miałem nadzieję, return false;że zatrzyma przewijanie strony - ale to po prostu sprawia, że ​​link w ogóle nie działa. Więc to na razie tylko zakomentowane, abym mógł nawigować.

Jakieś pomysły?

CBarr
źródło

Odpowiedzi:

111

Krok 1: Musisz rozbroić identyfikator węzła, dopóki hash nie zostanie ustawiony. Odbywa się to poprzez usunięcie ID z węzła podczas ustawiania skrótu, a następnie dodanie go z powrotem.

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

Krok 2: Niektóre przeglądarki uruchamiają przewijanie na podstawie tego, gdzie węzeł ID był ostatnio widziany, więc musisz im trochę pomóc. Musisz dodać dodatek divna górze widoku, ustawić jego identyfikator na hash, a następnie cofnąć wszystko:

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

Krok 3: Owiń go we wtyczkę i użyj tego zamiast pisać do location.hash...

Borgar
źródło
1
Nie, to, co dzieje się w kroku 2, polega na utworzeniu ukrytego elementu div i umieszczeniu go w bieżącym miejscu zwoju. Wizualnie jest to to samo, co pozycja: stała / górna: 0. W ten sposób pasek przewijania jest „przenoszony” dokładnie w to samo miejsce, w którym się obecnie znajduje.
Borgar
3
To rozwiązanie rzeczywiście działa dobrze - jednak linia: top: $ .scroll (). Top + 'px' Powinna być: top: $ (window) .scrollTop () + 'px'
Mark Perkins
27
Warto wiedzieć, które przeglądarki wymagają tutaj kroku 2.
djc
1
Dzięki Borgar. Zastanawiam się jednak, że dołączasz element div fx przed usunięciem identyfikatora węzła docelowego. Oznacza to, że istnieje chwila, w której w dokumencie znajdują się zduplikowane identyfikatory. Wygląda na potencjalny problem, a przynajmniej złe maniery;)
Ben
1
Dziękuję bardzo, to też mi pomogło. Ale zauważyłem również efekt uboczny, gdy to działa: często przewijam, blokując „środkowy przycisk myszy” i po prostu przesuwając mysz w kierunku, w którym chcę przewijać. W Google Chrome przerywa to przewijanie. Czy istnieje jakieś wymyślne obejście tego problemu?
YMMD
99

Użyj history.replaceStatelub history.pushState*, aby zmienić skrót. Nie spowoduje to przejścia do skojarzonego elementu.

Przykład

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

Demo na JSFiddle

* Jeśli chcesz obsługiwać historię do przodu i do tyłu

Zachowanie historyczne

Jeśli używasz history.pushStatei nie chcesz przewijać strony, gdy użytkownik korzysta z przycisków historii przeglądarki (do przodu / do tyłu), sprawdź scrollRestorationustawienie eksperymentalne (tylko Chrome 46+) .

history.scrollRestoration = 'manual';

Wsparcie przeglądarki

HaNdTriX
źródło
5
replaceStatejest prawdopodobnie lepszym sposobem, aby się tu dostać. Różnica polega na tym, że pushStatedodaje element do historii, a replaceStatenie.
Aakil Fernandes
Dzięki @AakilFernandes masz rację. Właśnie zaktualizowałem odpowiedź.
HaNdTriX
Nie zawsze jest to, bodyco się przewija, czasami jest to documentElement. Zobacz tę istotę Diego Perini
Matijs
1
masz jakiś pomysł na ie8 / 9 tutaj?
user151496
1
@ user151496 sprawdź: stackoverflow.com/revisions/ ...
HaNdTriX
90

Myślę, że mogłem znaleźć dość proste rozwiązanie. Problem polega na tym, że hash w adresie URL jest również elementem na stronie, do której zostanie przewinięty. jeśli po prostu dodam jakiś tekst do skrótu, teraz nie będzie on już odnosił się do istniejącego elementu!

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

Teraz adres URL wygląda tak, jakby page.aspx#btn_elementIDnie był prawdziwym identyfikatorem na stronie. Po prostu usuwam „btn_” i otrzymuję rzeczywisty identyfikator elementu

CBarr
źródło
5
Świetne rozwiązanie. Najbardziej bezbolesne.
Swader
Zwróć uwagę, że jeśli z jakiegoś powodu już zdecydowałeś się korzystać z adresów URL page.aspx # elementID, możesz odwrócić tę technikę i dołączyć „btn_” do wszystkich swoich identyfikatorów
joshuahedlund
2
Nie działa, jeśli używasz: target pseudo-selektorów w CSS.
Jason T Featheringham
1
Uwielbiam to rozwiązanie! Używamy skrótu adresu URL do nawigacji opartej na AJAX, poprzedzając identyfikatory znakiem /wydaje się logiczne.
Connell,
3

Fragment oryginalnego kodu:

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

Zmień to na:

$("#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
źródło
To nie zadziała, ponieważ ustawienie hashwłaściwości spowoduje przewijanie strony i tak.
jordanbtucker
3

Niedawno window.location.hashbudowałem karuzelę, która opiera się na utrzymywaniu stanu, i odkryłem, że przeglądarki Chrome i webkit wymuszą przewijanie (nawet do niewidocznego celu) z niezręcznym szarpnięciem, gdy window.onhashchangezdarzenie zostanie uruchomione .

Nawet próba zarejestrowania programu obsługi, który zatrzymuje propagację:

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

Nie zrobił nic, aby zatrzymać domyślne zachowanie przeglądarki. Rozwiązanie, które znalazłem, służyło window.history.pushStatedo zmiany skrótu bez wywoływania niepożądanych skutków ubocznych.

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

Następnie możesz nasłuchiwać zarówno normalnego zdarzenia hashchange, jak i my.hashchange:

$(window).on('hashchange my.hashchange', function(e, newUrl, oldUrl){
  // @todo - do something awesome!
});
max
źródło
Oczywiście my.hashchangeto tylko przykładowa nazwa wydarzenia
max
2

Okej, to dość stary temat, ale pomyślałem, że mogę się do niego włączyć, ponieważ „poprawna” odpowiedź nie działa dobrze z CSS.

To rozwiązanie w zasadzie zapobiega przesunięciu strony przez zdarzenie click, dzięki czemu możemy najpierw uzyskać pozycję przewijania. Następnie ręcznie dodajemy hash, a przeglądarka automatycznie wyzwala zdarzenie hashchange . Przechwytujemy zdarzenie hashchange i przewijamy z powrotem do właściwej pozycji. Wywołanie zwrotne oddziela i zapobiega opóźnieniu kodu przez trzymanie hakowania w jednym miejscu.

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
źródło
Nie potrzebujesz funkcji hashchange, po prostu natychmiast przewiń wstecz
daniel.gindi,
2

Erm, mam nieco prymitywną, ale zdecydowanie działającą metodę.
Po prostu zapisz bieżącą pozycję przewijania w zmiennej tymczasowej, a następnie zresetuj ją po zmianie hasha. :)

Więc dla oryginalnego przykładu:

$("#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
źródło
Problem z tą metodą polega na tym, że w przeglądarkach mobilnych można zauważyć, że przewijanie jest zmodyfikowane
Tofandel
1

Nie sądzę, żeby to było możliwe. O ile wiem, jedyną sytuacją, w której przeglądarka nie przewija do zmiany, document.location.hashjest brak hasha na stronie.

Ten artykuł nie jest bezpośrednio związany z Twoim pytaniem, ale omawia typowe zmiany zachowań przeglądarekdocument.location.hash

Ben S.
źródło
1

jeśli używasz zdarzenia hashchange z parserem hash, możesz zapobiec domyślnej akcji na linkach i zmienić lokalizację. hash dodawanie jednego znaku, aby mieć różnicę z właściwością id elementu

$('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
źródło
Idealny! Po pięciu godzinach próby rozwiązania problemu z przeładowaniem mieszania ...: / To była jedyna rzecz, która zadziałała !! Dzięki!!!
Igor Trindade
1

Dodaję to tutaj, ponieważ wszystkie bardziej trafne pytania zostały oznaczone jako duplikaty wskazujące tutaj…

Moja sytuacja jest prostsza:

  • użytkownik klika link ( a[href='#something'])
  • moduł obsługi kliknięć wykonuje: e.preventDefault()
  • funkcja smoothscroll: $("html,body").stop(true,true).animate({ "scrollTop": linkoffset.top }, scrollspeed, "swing" );
  • następnie window.location = link;

W ten sposób następuje przewijanie i nie ma przeskoku, gdy lokalizacja jest aktualizowana.

ptim
źródło
0

Innym sposobem na zrobienie tego jest dodanie elementu div, który jest ukryty w górnej części widoku. Ten element div jest następnie przypisywany do identyfikatora skrótu przed dodaniem go do adresu URL ... więc nie otrzymasz przewijania.

Mike Rifgin
źródło
0

Oto moje rozwiązanie dla kart z włączoną historią:

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

I aby pokazać ostatnio wybraną zakładkę:

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

Zwróć uwagę *=na filterrozmowę. Jest to rzecz specyficzna dla jQuery i bez niej karty z włączoną historią zawiodą.

user1429980
źródło
0

To rozwiązanie tworzy element div w rzeczywistym scrollTop i usuwa go po zmianie hasha:

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

opcjonalne, wyzwalające zdarzenie zakotwiczenia przy wczytywaniu strony:

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

źródło
0

Mam prostszą metodę, która działa dla mnie. Zasadniczo pamiętaj, czym właściwie jest hash w HTML. To link do zakotwiczenia tagu nazwy. Dlatego się przewija ... przeglądarka próbuje przewinąć do linku zakotwiczenia. Więc daj mu jeden!

  1. Zaraz pod tagiem BODY umieść swoją wersję tego:
 <a name="home"> </a> <a name="firstsection"> </a> <a name="secondsection"> </a> <a name="thirdsection"> </a>
  1. Nazwij elementy div sekcji za pomocą klas zamiast identyfikatorów.

  2. W kodzie przetwarzania usuń znak krzyżyka i zastąp kropką:

    var trimPanel = loadhash.substring (1); // stracić hash

    var dotSelect = '.' + trimPanel; // zamień krzyżyk na kropkę

    $ (dotSelect) .addClass ("activepanel"). show (); // pokaż element div powiązany z hashem.

Na koniec usuń element.preventDefault lub return: false i pozwól na uruchomienie nawigacji. Okno pozostanie na górze, skrót zostanie dołączony do adresu URL paska adresu i otworzy się właściwy panel.

ColdSharper
źródło
0

Myślę, że musisz zresetować przewijanie do jego pozycji przed zmianą 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
źródło
0

Jeśli na swojej stronie używasz identyfikatora jako pewnego rodzaju punktu zakotwiczenia i masz scenariusze, w których chcesz, aby użytkownicy dopisywali # coś na końcu adresu URL i przewijali stronę do tej sekcji # coś, używając własnej zdefiniowanej animacji funkcja javascript, detektor zdarzeń zmiany skrótu nie będzie w stanie tego zrobić.

Jeśli po prostu umieścisz debugger bezpośrednio po zdarzeniu hashchange, na przykład coś takiego (cóż, używam jquery, ale rozumiesz):

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

Zauważysz, że jak tylko zmienisz adres URL i naciśniesz przycisk Enter, strona natychmiast zatrzyma się w odpowiedniej sekcji, dopiero potem uruchomiona zostanie Twoja własna zdefiniowana funkcja przewijania i przewija się do tej sekcji, która wygląda bardzo źle.

Moja sugestia to:

  1. nie używaj id jako punktu zakotwiczenia do sekcji, do której chcesz przewinąć.

  2. Jeśli musisz użyć ID, tak jak ja. Zamiast tego użyj detektora zdarzeń „popstate”, nie będzie on automatycznie przewijał do tej samej sekcji, którą dołączasz do adresu URL, zamiast tego możesz wywołać własną zdefiniowaną funkcję wewnątrz zdarzenia popstate.

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

Na koniec musisz zrobić małą sztuczkę we własnej zdefiniowanej funkcji przewijania:

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

usuń identyfikator ze swojego tagu i zresetuj go.

To powinno załatwić sprawę.

Shuwei
źródło
-5

Dodaj ten kod do jQuery tylko wtedy, gdy dokument jest gotowy

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
źródło