Zapobiegaj przewijaniu przeglądarki w popstate historii HTML5

83

Czy można zapobiec domyślnemu zachowaniu przewijania dokumentu w przypadku wystąpienia zdarzenia popstate?

Nasza witryna wykorzystuje animowane przewijanie jQuery i History.js, a zmiany stanu powinny przewijać użytkownika do różnych obszarów strony, czy to poprzez pushstate, czy popstate. Problem polega na tym, że przeglądarka automatycznie przywraca pozycję przewijania z poprzedniego stanu, gdy wystąpi zdarzenie popstate.

Próbowałem użyć elementu kontenera ustawionego na 100% szerokości i wysokości dokumentu i przewijać zawartość w tym kontenerze. Problem z tym, który znalazłem, polega na tym, że nie wydaje się być tak płynny jak przewijanie dokumentu; zwłaszcza jeśli używasz wielu css3, takich jak cienie i gradienty.

Próbowałem również zapisać pozycję przewijania dokumentu podczas przewijania zainicjowanego przez użytkownika i przywrócić go po przewinięciu strony przez przeglądarkę (na popstate). Działa to dobrze w przeglądarce Firefox 12, ale w przeglądarce Chrome 19 pojawia się migotanie spowodowane przewijaniem i przywracaniem strony. Zakładam, że ma to związek z opóźnieniem między przewijaniem a uruchomieniem zdarzenia przewijania (gdzie przywracana jest pozycja przewijania).

Firefox przewija stronę (i uruchamia zdarzenie przewijania) przed uruchomieniem funkcji popstate, a Chrome uruchamia najpierw polecenie popstate, a następnie przewija dokument.

Wszystkie strony, które widziałem, które używają API historii albo używają rozwiązania podobnego do tych powyżej, albo po prostu ignorują zmianę pozycji przewijania, gdy użytkownik przechodzi do tyłu / do przodu (np. GitHub).

Czy można w ogóle zapobiec przewijaniu dokumentu podczas zdarzeń popstate?

Alex Malet de Carteret
źródło
Możesz odnieść sukces w czytaniu wymiaru elementu (aby wymusić ponowne wlanie strony) po ustawieniu pozycji przewijania. np. document.body.clientHeightale nie widziałem, żeby to działało we wszystkich przeglądarkach.
Sean Hogan
A co by było, gdybyś manipulował funkcją przewijania dokumentu, od tego przynajmniej bym zaczął. Jest na to rozwiązanie tutaj: stackoverflow.com/questions/7035331/…
Jeff Wooden

Odpowiedzi:

67
if ('scrollRestoration' in history) {
  history.scrollRestoration = 'manual';
}

( Ogłoszone przez Google 2 września 2015 r.)

Obsługa przeglądarek:

Chrome: obsługiwany (od 46)

Firefox: obsługiwany (od 46)

IE: nieobsługiwane

Edge: obsługiwane (od 79)

Opera: obsługiwana (od 33)

Safari: obsługiwane

Aby uzyskać więcej informacji, zobacz Zgodność przeglądarek na MDN .

Tamás Bolvári
źródło
1
Krótszy: history.scrollRestoration && history.scrollRestoration = 'manual';lub nawetvar sr = 'scrollRestoration'; history[sr] && history[sr] = 'manual';
Bharata
31

Jest to problem zgłaszany w rdzeniu deweloperskim Mozilli od ponad roku . Niestety bilet nie poszedł tak naprawdę. Myślę, że Chrome jest taki sam: nie ma niezawodnego sposobu radzenia sobie z pozycją przewijania onpopstateza pomocą js, ponieważ jest to natywne zachowanie przeglądarki.

Jest jednak nadzieja na przyszłość, jeśli spojrzysz na specyfikację historii HTML5 , która wyraźnie życzy sobie, aby pozycja przewijania była reprezentowana w obiekcie stanu:

Obiekty historii przedstawiają historię sesji ich kontekstu przeglądania jako płaską listę wpisów historii sesji. Każdy wpis historii sesji składa się z adresu URL i opcjonalnie obiektu stanu i może dodatkowo mieć tytuł, obiekt Document, dane formularza, pozycję przewijania i inne powiązane z nim informacje.

To, i jeśli czytasz komentarze do wspomnianego powyżej biletu Mozilli, daje pewną wskazówkę, że jest możliwe, że w niedalekiej przyszłości pozycja przewijania nie zostanie już przywrócona onpopstate, przynajmniej dla osób używającychpushState .

Niestety, do tego czasu pozycja przewijania jest zapisywana, gdy pushStatejest używana i replaceStatenie zastępuje pozycji przewijania. W przeciwnym razie byłoby to dość łatwe i można by użyć replaceStatedo ustawienia bieżącej pozycji przewijania za każdym razem, gdy użytkownik przewinie stronę (z jakąś ostrożną obsługą przewijania).

Niestety, specyfikacja HTML5 nie określa, kiedy dokładnie popstatezdarzenie ma zostać uruchomione , po prostu mówi: „jest uruchamiane w niektórych przypadkach podczas przechodzenia do wpisu historii sesji”, co nie określa jasno, czy ma to miejsce przed, czy po; gdyby tak było zawsze wcześniej, możliwe byłoby rozwiązanie obsługujące zdarzenie przewijania występujące po popstate.

Anulować zdarzenie przewijania?

Co więcej, byłoby to również łatwe, gdyby zdarzenie przewijania było możliwe do anulowania, a nie jest. Gdyby tak było, możesz po prostu anulować pierwsze zdarzenie przewijania w serii (zdarzenia przewijania użytkownika są jak lemingi, występują w dziesiątkach, podczas gdy zdarzenie przewijania uruchomione przez repozycjonowanie historii jest pojedyncze) i byłoby dobrze.

Na razie nie ma rozwiązania

O ile widzę, jedyną rzeczą, którą na razie zalecam, jest poczekanie na pełne zaimplementowanie specyfikacji HTML5 i uruchomienie z zachowaniem przeglądarki w tym przypadku, co oznacza: animuj przewijanie, gdy przeglądarka na to pozwoli i pozwól przeglądarce zmienić położenie strony, gdy wystąpi zdarzenie historii. Jedyną rzeczą, na którą możesz wpływać pod względem pozycji, jest to, że używasz, pushStategdy strona jest pozycjonowana w dobry sposób, aby wrócić. Każde inne rozwiązanie może zawierać błędy lub być zbyt specyficzne dla przeglądarki, lub jedno i drugie.

Pokonaj Richartz
źródło
Sformułowanie specyfikacji historii jest teraz nieco inne. Ponadto, jeśli przewiniesz w dół i spojrzysz na dokumentację pushState, nie ma wzmianki o dodaniu pozycji przewijania. Specyfikacja sugeruje, że wpis historii sesji może zawierać zapisaną pozycję przewijania, ale nie jestem pewien, czy to oznacza, że ​​programista internetowy będzie mógł to ustawić.
Walex
4

Będziesz musiał użyć tutaj jakiegoś okropnego węszenia przeglądarki. W przypadku przeglądarki Firefox wybrałbym rozwiązanie polegające na przechowywaniu pozycji przewijania i przywracaniu jej.

Wydawało mi się, że mam dobre rozwiązanie Webkit na podstawie twojego opisu, ale właśnie wypróbowałem w Chrome 21 i wygląda na to, że Chrome najpierw przewija, a następnie uruchamia zdarzenie popstate, a następnie zdarzenie przewijania. Ale dla odniesienia, oto co wymyśliłem:

function noScrollOnce(event) {
    event.preventDefault();
    document.removeEventListener('scroll', noScrollOnce);
}
window.onpopstate = function () {
    document.addEventListener('scroll', noScrollOnce);
};​

Czarna magia, taka jak udawanie, że strona przewija się przez przesuwanie absoluteumieszczonego elementu, jest również wykluczana przez szybkość odświeżania ekranu.

Jestem więc w 99% pewien, że odpowiedź brzmi, że nie możesz i będziesz musiał skorzystać z jednego z kompromisów, o których wspomniałeś w pytaniu. Obie przeglądarki przewijają się, zanim JavaScript cokolwiek o tym wie, więc JavaScript może zareagować dopiero po zdarzeniu. Jedyna różnica polega na tym, że Firefox maluje ekran dopiero po uruchomieniu skryptu Javascript, dlatego w Firefoksie istnieje praktyczne rozwiązanie, ale nie w WebKit.

Nathan MacInnes
źródło
3

Teraz możesz to zrobić

history.scrollRestoration = 'manual';

a to powinno zapobiec przewijaniu przeglądarki. Działa to teraz tylko w Chrome 46 i nowszych, ale wydaje się, że Firefox planuje również to obsługiwać

Roman Usherenko
źródło
2

Rozwiązaniem jest użycie position: fixedi określenietop równej pozycji przewijania strony.

Oto przykład:

$(window).on('popstate', function()
{
   $('.yourWrapAroundAllContent').css({
      position: 'fixed',
      top: -window.scrollY
   });

   requestAnimationFrame(function()
   {
      $('.yourWrapAroundAllContent').css({
         position: 'static',
         top: 0
      });          
   });
});

Tak, zamiast tego otrzymujesz migający pasek przewijania, ale jest to mniej złe.

mshipov
źródło
1

Poniższa poprawka powinna działać we wszystkich przeglądarkach.

Możesz ustawić pozycję przewijania na 0 w zdarzeniu rozładowania. Możesz przeczytać o tym wydarzeniu tutaj: https://developer.mozilla.org/en-US/docs/Web/Events/unload . Zasadniczo zdarzenie rozładowania jest uruchamiane tuż przed opuszczeniem strony.

Ustawienie scrollPosition na 0 przy wyładowaniu oznacza, że ​​gdy opuścisz stronę z ustawionym pushState, ustawia ona scrollPosition na 0. Kiedy wrócisz do tej strony poprzez odświeżenie lub naciśnięcie wstecz, nie nastąpi automatyczne przewijanie.

//Listen for unload event. This is triggered when leaving the page.
//Reference: https://developer.mozilla.org/en-US/docs/Web/Events/unload
window.addEventListener('unload', function(e) {
    //set scroll position to the top of the page.
    window.scrollTo(0, 0);
});
Ben Bud
źródło
1

Ustawienie scrollRestoration na manual nie zadziałało, oto moje rozwiązanie.

window.addEventListener('popstate', function(e) {
    var scrollTop = document.body.scrollTop;
    window.addEventListener('scroll', function(e) {
        document.body.scrollTop = scrollTop;
    });
});
rames
źródło
0

Utwórz element „span” gdzieś u góry strony i ustaw na nim fokus podczas ładowania. Przeglądarka przewinie do zogniskowanego elementu. Rozumiem, że jest to obejście i skupienie się na „span” nie działa we wszystkich przeglądarkach (hmmm… Safari). Mam nadzieję że to pomoże.

Prashant Saraswat
źródło
0

Oto, co zaimplementowałem w witrynie, która chciała, aby pozycja przewijania skupiała się na określonym elemencie po uruchomieniu poststate (przycisk Wstecz):

$(document).ready(function () {

     if (window.history.pushState) {
        //if push supported - push current page onto stack:
        window.history.pushState(null, document.title, document.location.href);
    }

    //add handler:
    $(window).on('popstate', PopStateHandler);
}

//fires when the back button is pressed:
function PopStateHandler(e) {
    emnt= $('#elementID');
    window.scrollTo(0, emnt.position().top);
    alert('scrolling to position');
}

Przetestowane i działa w przeglądarce Firefox.

Chrome przewinie do pozycji, ale następnie wróci do pierwotnego miejsca.

George Filippakos
źródło
0

Jak mówili inni, nie ma prawdziwego sposobu, aby to zrobić, tylko brzydki, hakerski sposób. Usunięcie nasłuchiwania zdarzeń przewijania nie zadziałało, więc oto mój brzydki sposób na zrobienie tego:

/// GLOBAL VARS ////////////////////////
var saveScrollPos = false;
var scrollPosSaved = window.pageYOffset;
////////////////////////////////////////

jQuery(document).ready(function($){

    //// Go back with keyboard shortcuts ////
    $(window).keydown(function(e){
        var key = e.keyCode || e.charCode;

        if((e.altKey && key == 37) || (e.altKey && key == 39) || key == 8)
        saveScrollPos = false;
    });
    /////////////////////////////////////////

    //// Go back with back button ////
    $("html").bind("mouseout",  function(){ saveScrollPos = false; });
    //////////////////////////////////

    $("html").bind("mousemove", function(){ saveScrollPos = true; });

    $(window).scroll(function(){
        if(saveScrollPos)
        scrollPosSaved = window.pageYOffset;
        else
        window.scrollTo(0, scrollPosSaved);
    });

});

Działa w Chrome, FF i IE (miga przy pierwszym powrocie do IE). Wszelkie sugestie dotyczące ulepszeń są mile widziane! Mam nadzieję że to pomoże.

pmrotule
źródło
-5

Chcesz tego spróbować?

window.onpopstate = function(event) {
  return false;
};
Dan Barzilay
źródło
3
To nie działa. Niektórym zdarzeniom nie można zapobiec, a popstate jest jednym z nich.
Nathan MacInnes,