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?
źródło
document.body.clientHeight
ale nie widziałem, żeby to działało we wszystkich przeglądarkach.Odpowiedzi:
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 .
źródło
history.scrollRestoration && history.scrollRestoration = 'manual';
lub nawetvar sr = 'scrollRestoration'; history[sr] && history[sr] = 'manual';
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
onpopstate
za 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:
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
pushState
jest używana ireplaceState
nie zastępuje pozycji przewijania. W przeciwnym razie byłoby to dość łatwe i można by użyćreplaceState
do 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
popstate
zdarzenie 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,
pushState
gdy 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.źródło
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
absolute
umieszczonego 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.
źródło
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ć
źródło
Rozwiązaniem jest użycie
position: fixed
i 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.
źródło
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); });
źródło
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; }); });
źródło
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.
źródło
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.
źródło
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.
źródło
Chcesz tego spróbować?
window.onpopstate = function(event) { return false; };
źródło