Używam interfejsu API historii dla mojej aplikacji internetowej i mam jeden problem. Wykonuję wywołania Ajax, aby zaktualizować niektóre wyniki na stronie i używam history.pushState () w celu zaktualizowania paska adresu przeglądarki bez ponownego ładowania strony. Następnie oczywiście używam window.popstate w celu przywrócenia poprzedniego stanu po kliknięciu przycisku wstecz.
Problem jest dobrze znany - Chrome i Firefox inaczej traktują to zdarzenie popstate. Chociaż Firefox nie uruchamia go przy pierwszym załadowaniu, Chrome tak. Chciałbym mieć styl Firefoksa i nie uruchamiać zdarzenia podczas ładowania, ponieważ po prostu aktualizuje wyniki przy użyciu dokładnie takich samych wyników podczas ładowania. Czy istnieje obejście problemu z wyjątkiem używania History.js ? Powodem, dla którego nie mam ochoty go używać, jest to, że sam potrzebuje zbyt wielu bibliotek JS, a ponieważ potrzebuję go zaimplementować w CMS z już zbyt dużą ilością JS, chciałbym zminimalizować JS, w to wstawiam .
Więc chciałbym wiedzieć, czy istnieje sposób, aby Chrome nie uruchamiał się „popstate” podczas ładowania, czy może ktoś próbował użyć History.js, ponieważ wszystkie biblioteki zostały połączone w jeden plik.
Odpowiedzi:
W Google Chrome w wersji 19 przestało działać rozwiązanie z @spliter. Jak zauważył @johnnymire, history.state w Chrome 19 istnieje, ale jest zerowe.
Moim obejściem jest dodanie window.history.state! == null do sprawdzania, czy stan istnieje w window.history:
var popped = ('state' in window.history && window.history.state !== null), initialURL = location.href;
Przetestowałem to we wszystkich głównych przeglądarkach oraz w Chrome w wersjach 19 i 18. Wygląda na to, że działa.
źródło
in
inull
sprawdzić,!= null
ponieważ to również zajmieundefined
.null
w swoichhistory.pushState()
metodach, takich jakhistory.pushState(null,null,'http//example.com/)
. To prawda, to prawdopodobnie większość zastosowań (tak to jest ustawione w jquery.pjax.js i większości innych dem). Ale jeśli przeglądarki zaimplementująwindow.history.state
(jak FF i Chrome 19+), window.history.state może mieć wartość różną od zera po odświeżeniu lub po ponownym uruchomieniu przeglądarkiJeśli nie chcesz podejmować specjalnych działań dla każdego handlera, którego dodajesz do onpopstate, moje rozwiązanie może być dla Ciebie interesujące. Dużym plusem tego rozwiązania jest również to, że zdarzenia onpopstate mogą być obsługiwane przed zakończeniem ładowania strony.
Po prostu uruchom ten kod raz, zanim dodasz jakiekolwiek programy obsługi onpopstate i wszystko powinno działać zgodnie z oczekiwaniami (podobnie jak w Mozilli ^^) .
(function() { // There's nothing to do for older browsers ;) if (!window.addEventListener) return; var blockPopstateEvent = document.readyState!="complete"; window.addEventListener("load", function() { // The timeout ensures that popstate-events will be unblocked right // after the load event occured, but not in the same event-loop cycle. setTimeout(function(){ blockPopstateEvent = false; }, 0); }, false); window.addEventListener("popstate", function(evt) { if (blockPopstateEvent && document.readyState=="complete") { evt.preventDefault(); evt.stopImmediatePropagation(); } }, false); })();
Jak to działa:
Chrome, Safari i prawdopodobnie inne przeglądarki webkit uruchamiają zdarzenie onpopstate po załadowaniu dokumentu. Nie jest to zamierzone, więc blokujemy zdarzenia popstate aż do pierwszego cyklu pętli zdarzeń po załadowaniu dokumentu. Odbywa się to za pomocą wywołań disableDefault i stopImmediatePropagation (w przeciwieństwie do stopPropagation stopImmediatePropagation natychmiast zatrzymuje wszystkie wywołania obsługi zdarzeń).Jednak ponieważ readyState dokumentu jest już w stanie „complete”, gdy Chrome uruchamia się błędnie w trybie popstate, zezwalamy na zdarzenia opopstate, które były uruchamiane przed zakończeniem ładowania dokumentu, aby umożliwić wywołania onpopstate przed załadowaniem dokumentu.
Aktualizacja 2014-04-23: Naprawiono błąd, w którym zdarzenia popstate były blokowane, jeśli skrypt jest wykonywany po załadowaniu strony.
źródło
window.history.state
nie powiedzie się przy przeładowaniu, jeśli ustawiono stan. Ustawienie zmiennej przed pushState zaśmieca kod. Twoje rozwiązanie jest eleganckie i można je upuścić na inne skrypty, bez wpływu na resztę kodu. Dzięki.Używanie tylko setTimeout nie jest poprawnym rozwiązaniem, ponieważ nie masz pojęcia, ile czasu zajmie załadowanie zawartości, więc możliwe jest, że zdarzenie popstate jest emitowane po przekroczeniu limitu czasu.
Oto moje rozwiązanie: https://gist.github.com/3551566
/* * Necessary hack because WebKit fires a popstate event on document load * https://code.google.com/p/chromium/issues/detail?id=63040 * https://bugs.webkit.org/process_bug.cgi */ window.addEventListener('load', function() { setTimeout(function() { window.addEventListener('popstate', function() { ... }); }, 0); });
źródło
Rozwiązanie zostało znalezione w jquery.pjax.js liniach 195-225:
// Used to detect initial (useless) popstate. // If history.state exists, assume browser isn't going to fire initial popstate. var popped = ('state' in window.history), initialURL = location.href // popstate handler takes care of the back and forward buttons // // You probably shouldn't use pjax on pages with other pushState // stuff yet. $(window).bind('popstate', function(event){ // Ignore inital popstate that some browsers fire on page load var initialPop = !popped && location.href == initialURL popped = true if ( initialPop ) return var state = event.state if ( state && state.pjax ) { var container = state.pjax if ( $(container+'').length ) $.pjax({ url: state.url || location.href, fragment: state.fragment, container: container, push: false, timeout: state.timeout }) else window.location = location.href } })
źródło
Bardziej bezpośrednim rozwiązaniem niż ponowna implementacja pjax jest ustawienie zmiennej w pushState i sprawdzenie zmiennej w popState, aby początkowe polecenie popState nie było niespójnie uruchamiane podczas ładowania (nie jest to rozwiązanie specyficzne dla jquery, po prostu używanie go do zdarzeń):
$(window).bind('popstate', function (ev){ if (!window.history.ready && !ev.originalEvent.state) return; // workaround for popstate on load }); // ... later ... function doNavigation(nextPageId) { window.history.ready = true; history.pushState(state, null, 'content.php?id='+ nextPageId); // ajax in content instead of loading server-side }
źródło
window.history.ready
zawsze jest prawdą.!window.history.ready && !ev.originalEvent.state
- te dwie rzeczy nigdy nie są definiowane, kiedy odświeżam stronę. dlatego żaden kod nie jest wykonywany.Początkowe zdarzenie onpopstate Webkit nie ma przypisanego stanu, więc możesz użyć tego do sprawdzenia niepożądanego zachowania:
window.onpopstate = function(e){ if(e.state) //do something };
Kompleksowe rozwiązanie, pozwalające na nawigację z powrotem do oryginalnej strony, opierałoby się na tym pomyśle:
<body onload="init()"> <a href="page1" onclick="doClick(this); return false;">page 1</a> <a href="page2" onclick="doClick(this); return false;">page 2</a> <div id="content"></div> </body> <script> function init(){ openURL(window.location.href); } function doClick(e){ if(window.history.pushState) openURL(e.getAttribute('href'), true); else window.open(e.getAttribute('href'), '_self'); } function openURL(href, push){ document.getElementById('content').innerHTML = href + ': ' + (push ? 'user' : 'browser'); if(window.history.pushState){ if(push) window.history.pushState({href: href}, 'your page title', href); else window.history.replaceState({href: href}, 'your page title', href); } } window.onpopstate = function(e){ if(e.state) openURL(e.state.href); }; </script>
Chociaż nadal może to wystrzelić dwukrotnie (z pewną sprytną nawigacją), można to zrobić po prostu sprawdzając poprzedni href.
źródło
window.history.state
w iOS jest również zerowe. Wystarczy sprawdzić, czy właściwość e.state ma wartość null.To jest moje obejście.
window.setTimeout(function() { window.addEventListener('popstate', function() { // ... }); }, 1000);
źródło
Oto moje rozwiązanie:
var _firstload = true; $(function(){ window.onpopstate = function(event){ var state = event.state; if(_firstload && !state){ _firstload = false; } else if(state){ _firstload = false; // you should pass state.some_data to another function here alert('state was changed! back/forward button was pressed!'); } else{ _firstload = false; // you should inform some function that the original state returned alert('you returned back to the original state (the home state)'); } } })
źródło
Najlepszym sposobem, aby Chrome nie uruchamiał popstate podczas ładowania strony, jest głosowanie pod adresem https://code.google.com/p/chromium/issues/detail?id=63040 . Od dwóch lat wiedzieli, że Chrome nie jest zgodny ze specyfikacją HTML5 i nadal tego nie naprawili!
źródło
W przypadku użycia
event.state !== null
powrót do historii do pierwszej załadowanej strony nie będzie działać w przeglądarkach innych niż mobilne. Używam sessionStorage, aby zaznaczyć, kiedy naprawdę zaczyna się nawigacja Ajax.history.pushState(url, null, url); sessionStorage.ajNavStarted = true; window.addEventListener('popstate', function(e) { if (sessionStorage.ajNavStarted) { location.href = (e.state === null) ? location.href : e.state; } }, false);
źródło
Przedstawione rozwiązania mają problem z przeładowaniem strony. Poniższe wydaje się działać lepiej, ale testowałem tylko przeglądarki Firefox i Chrome. Wykorzystuje fakt, że wydaje się istnieć różnica między
e.event.state
iwindow.history.state
.window.addEvent('popstate', function(e) { if(e.event.state) { window.location.reload(); // Event code } });
źródło
e.event
jest niezdefiniowaneWiem, że sprzeciwiłeś się temu, ale naprawdę powinieneś po prostu użyć History.js, ponieważ usuwa milion niezgodności przeglądarek. Poszedłem ręczną trasą naprawczą tylko po to, aby później odkryć, że jest coraz więcej problemów, o których dowiesz się dopiero po drodze. To naprawdę nie jest takie trudne w dzisiejszych czasach:
<script src="//cdnjs.cloudflare.com/ajax/libs/history.js/1.8/native.history.min.js" type="text/javascript"></script>
Przeczytaj interfejs API pod adresem https://github.com/browserstate/history.js
źródło
To rozwiązało problem. Wszystko, co zrobiłem, to ustawienie funkcji limitu czasu, która opóźnia wykonanie funkcji na tyle długo, aby przegapić zdarzenie popstate uruchamiane podczas ładowania strony
if (history && history.pushState) { setTimeout(function(){ $(window).bind("popstate", function() { $.getScript(location.href); }); },3000); }
źródło
Możesz utworzyć zdarzenie i uruchomić je po obsłudze ładowania.
var evt = document.createEvent("PopStateEvent"); evt.initPopStateEvent("popstate", false, false, { .. state object ..}); window.dispatchEvent(evt);
Uwaga, w Chrome / Safari jest to trochę zepsute, ale przesłałem łatkę do WebKit i powinna być wkrótce dostępna, ale jest to „najbardziej poprawny” sposób.
źródło
To działało dla mnie w Firefoksie i Chrome
window.onpopstate = function(event) { //back button click console.log("onpopstate"); if (event.state) { window.location.reload(); } };
źródło