Historia HTML5 daje programistom możliwość modyfikowania adresu URL strony bez pełnego odświeżania strony. Jest to szczególnie przydatne w przypadku ładowania części strony za pomocą JavaScript, gdy treść jest znacząco inna i wymaga nowego adresu URL.
Oto przykład. Załóżmy, że osoba przechodzi ze strony głównej witryny na stronę pomocy. Ładujemy zawartość tej strony Pomocy za pomocą Ajaxa. Następnie użytkownik przechodzi do strony Produkty, którą ponownie ładujemy i wymieniamy zawartość za pomocą Ajaxa. Następnie chce udostępnić adres URL. Dzięki History API, moglibyśmy zmieniać adres URL strony wraz z użytkownikiem podczas nawigacji, więc adres URL, który widzi (a więc udostępnia lub zapisuje) jest odpowiedni i poprawny.
Podstawy
Aby sprawdzić funkcje tego API, wystarczy przejść do Narzędzi dla programistów i wpisać history
w konsoli. Jeśli API jest obsługiwane przez wybraną przeglądarkę, do tego obiektu dołączone są metody:
W tym tutorialu interesują nas metody pushState
oraz replaceState
. Wracając do konsoli, możemy trochę poeksperymentować z tymi metodami i zobaczyć, co się stanie z adresem URL, gdy ich użyjemy. Pozostałe parametry tej funkcji omówimy później, ale na razie musimy użyć tylko ostatniego parametru:
history.replaceState(null, null, 'hello');
Powyższa metoda replaceState
zamienia adres URL w pasku adresu na '/hello', mimo że nie są wymagane żadne zasoby, a okno pozostaje na tej samej stronie. Pojawia się tu jednak pewien problem. Po naciśnięciu przycisku wstecz, zauważymy, że nie wrócimy do adresu URL tego artykułu, ale zamiast tego wrócimy do strony, na której byliśmy wcześniej. Dzieje się tak, ponieważ replaceState
nie manipuluje historią przeglądarki, a jedynie zastępuje bieżący adres URL w pasku adresu.
Aby to naprawić, musimy użyć metody pushState
zamiast tego:
history.pushState(null, null, 'hello');
Teraz, jeśli klikniemy na przycisk wstecz, powinien on działać tak, jak byśmy chcieli, ponieważ pushState
zmienił naszą historię tak, aby zawierała adres URL, który właśnie do niej wprowadziliśmy. Jest to interesujące, ale co się stanie, jeśli spróbujemy czegoś nieco przebiegłego i udamy, że aktualny adres URL nie był wcale css-tricks.com, ale zupełnie inną stroną internetową?
history.pushState(null, null, 'https://twitter.com/hello');
To rzuci wyjątek, ponieważ adres URL musi być tego samego pochodzenia co aktualny, w przeciwnym razie możemy ryzykować poważne błędy w zabezpieczeniach i dać programistom możliwość oszukiwania ludzi, aby uwierzyli, że byli na zupełnie innej stronie.
Powracając do pozostałych parametrów, które są przekazywane do tej metody, możemy je podsumować w następujący sposób:
history.pushState(, , );
- Pierwszy parametr to dane, których będziemy potrzebować, jeśli stan strony się zmieni, na przykład za każdym razem, gdy ktoś naciśnie przycisk wstecz lub do przodu w swojej przeglądarce. Zauważ, że w Firefoksie dane te są ograniczone do 640k znaków.
-
title
jest drugim parametrem, który może być łańcuchem znaków, ale w momencie pisania tego tekstu, każda przeglądarka po prostu go ignoruje. - Ten ostatni parametr to adres URL, który chcemy, aby pojawił się w pasku adresu.
Szybka historia
Najważniejszą rzeczą w tych API historii jest to, że nie przeładowują one strony. W przeszłości jedynym sposobem na zmianę adresu URL była zmiana window.location
, która zawsze przeładowywała stronę. Z wyjątkiem sytuacji, gdy wszystko, co zmieniłeś, to hash
(jak kliknięcie <a href="#target">link</a>
nie przeładowuje strony).
To prowadziło do starej metody hashbang zmiany adresu URL bez pełnego odświeżenia strony. Słynny Twitter robił rzeczy w ten sposób i został za to w dużej mierze skrytykowany (hash nie jest „prawdziwą” lokalizacją zasobu).
Twitter odszedł od tego i był jednym z wczesnych zwolenników tego API. W 2012 roku zespół opisał swoje nowe podejście. Tutaj przedstawiają niektóre z problemów, z jakimi borykają się podczas pracy na taką skalę, a jednocześnie szczegółowo opisują, jak różne przeglądarki implementują tę specyfikację.
Przykład z wykorzystaniem pushState i Ajaxa
Zbudujmy demo!
W naszym wymyślonym interfejsie chcemy, aby użytkownicy naszej strony znaleźli informacje o postaci z filmu Ghostbusters. Gdy wybiorą obrazek, tekst o tej postaci powinien pojawić się pod nim, a także chcemy dodać aktualną klasę do każdego obrazka, aby było jasne, kto został wybrany. Następnie, gdy klikniemy przycisk wstecz, aktualna klasa przeskoczy do poprzednio wybranej postaci (i vice-versa dla przycisku do przodu) i oczywiście będziemy potrzebować zawartości pod spodem, aby również przełączyć się z powrotem.
Oto działający przykład, który możemy rozebrać na czynniki pierwsze:
Markup dla tego przykładu jest dość prosty: mamy .gallery
, który zawiera kilka linków, a w obrębie każdego z nich znajduje się obrazek. Poniżej mamy tekst, który chcemy zaktualizować o wybrane imię i nazwisko oraz pusty .content
div, który chcemy zastąpić danymi z plików HTML każdej z postaci:
<div class="gallery"> <a href="/peter.html"> <img src="bill.png" alt="Peter" class="peter" data-name="peter"> </a> <a href="/ray.html"> <img src="ray.png" alt="Ray" class="ray" data-name="ray"> </a> <a href="/egon.html"> <img src="egon.png" alt="Egon" class="egon" data-name="egon"> </a> <a href="/winston.html"> <img src="winston.png" alt="Winston" class="winston" data-name="winston"> </a></div><p class="selected">Ghostbusters</p><p class="highlight"></p><div class="content"></div>
Bez żadnego JavaScriptu ta strona nadal będzie działać tak jak powinna, kliknięcie linku kieruje na właściwą stronę, a kliknięcie przycisku wstecz również działa tak, jak oczekiwałby tego użytkownik. Yay dla dostępności i zgrabnej degradacji!
Następnie przejdziemy do JavaScript, gdzie możemy zacząć dodawać obsługę zdarzeń do każdego linku wewnątrz elementu .gallery
używając propagacji zdarzeń, tak jak poniżej:
var container = document.querySelector('.gallery');container.addEventListener('click', function(e) { if (e.target != e.currentTarget) { e.preventDefault(); // e.target is the image inside the link we just clicked. } e.stopPropagation();}, false);
Wewnątrz tej instrukcji if
możemy następnie przypisać atrybut data-name
wybranego przez nas obrazka do zmiennej data
. Następnie dołączymy do niej „.html” i użyjemy jej jako trzeciego parametru, adresu URL, który chcemy załadować, w naszej metodzie pushState
(chociaż w prawdziwym przykładzie prawdopodobnie chcielibyśmy zmienić adres URL dopiero po pomyślnym wykonaniu żądania Ajax):
var data = e.target.getAttribute('data-name'), url = data + ".html"; history.pushState(null, null, url); // here we can fix the current classes // and update text with the data variable // and make an Ajax request for the .content element // finally we can manually update the document’s title
(Alternatywnie, moglibyśmy również wykorzystać do tego atrybut href łącza.)
Zastąpiłem działający kod komentarzami, abyśmy mogli skupić się na metodzie pushState
na razie.
W tym momencie, kliknięcie na obrazek zaktualizuje pasek URL i zawartość z żądaniem Ajaxa, ale kliknięcie przycisku wstecz nie odeśle nas do poprzedniej postaci, którą wybraliśmy. To co musimy tutaj zrobić, to wykonać kolejne żądanie Ajax, gdy użytkownik kliknie przycisk wstecz/do przodu, a następnie będziemy musieli ponownie zaktualizować adres URL za pomocą pushState
.
Najpierw cofniemy się i zaktualizujemy parametr stanu w naszej metodzie pushState
, aby zachować te informacje:
history.pushState(data, null, url);
To jest pierwszy parametr, data
w powyższej metodzie. Teraz wszystko, co zostanie ustawione w tej zmiennej, będzie dostępne dla nas w zdarzeniu popstate
, które zostanie wywołane za każdym razem, gdy użytkownik kliknie na przyciski do przodu lub do tyłu.
window.addEventListener('popstate', function(e) { // e.state is equal to the data-attribute of the last image we clicked});
Następnie możemy wykorzystać te informacje w dowolny sposób, co w tym przypadku oznacza przekazanie nazwy poprzedniego Ghostbustera, którego wybraliśmy, jako parametru do funkcji Ajax requestContent
, która wykorzystuje metodę jQuery load
:
function requestContent(file) { $('.content').load(file + ' .content');}window.addEventListener('popstate', function(e) { var character = e.state; if (character == null) { removeCurrentClass(); textWrapper.innerHTML = " "; content.innerHTML = " "; document.title = defaultTitle; } else { updateText(character); requestContent(character + ".html"); addCurrentClass(character); document.title = "Ghostbuster | " + character; }});
Jeśli użytkownik kliknąłby na obrazek Raya, odpaliłby się nasz listener zdarzeń, który następnie zapisałby atrybut danych naszego obrazka w ramach zdarzenia pushState
. W konsekwencji spowoduje to załadowanie pliku ray.html
, który zostanie wywołany, jeśli użytkownik wybierze inny obrazek, a następnie kliknie przycisk wstecz.
Co nam pozostaje? Cóż, jeśli klikniemy na znak, a następnie udostępnimy adres URL, który zaktualizowaliśmy, wtedy ten plik HTML zostanie załadowany zamiast niego. Może to być mniej mylące doświadczenie i zachowamy integralność naszych adresów URL, jednocześnie dając naszym użytkownikom szybsze przeglądanie stron.
Ważne jest, aby przyznać, że powyższy przykład jest uproszczony, ponieważ ładowanie treści w ten sposób za pomocą jQuery jest bardzo niechlujne i prawdopodobnie chcielibyśmy przekazać bardziej złożony obiekt do naszej metody pushState
, ale pokazuje nam, jak możemy natychmiast zacząć uczyć się, jak korzystać z API historii. Najpierw chodzimy, potem biegamy.
Jeśli mielibyśmy użyć tej techniki na większą skalę to prawdopodobnie powinniśmy rozważyć użycie narzędzia zaprojektowanego specjalnie do tego celu. Na przykład pjax jest wtyczką jQuery, która przyspiesza proces używania Ajaxa i pushState jednocześnie, chociaż obsługuje tylko przeglądarki, które używają History API.
History JS z drugiej strony obsługuje starsze przeglądarki ze starym hash-fallback w adresach URL.
Fajne adresy URL
Lubię myśleć o adresach URL, a w szczególności cały czas odwołuję się do tego wpisu o projektowaniu adresów URL autorstwa Kyle’a Neatha:
URL są uniwersalne. Działają w Firefoxie, Chrome, Safari, Internet Explorerze, cURL, wget, iPhone, Android, a nawet zapisane na karteczkach samoprzylepnych. Są jedyną uniwersalną składnią sieci. Nie bierz tego za pewnik. Każdy zwykły półtechniczny użytkownik Twojej strony powinien być w stanie poruszać się po 90% Twojej aplikacji w oparciu o pamięć struktury adresów URL. Aby to osiągnąć, twoje adresy URL muszą być pragmatyczne.
To oznacza, że niezależnie od wszelkich hacków i sztuczek zwiększających wydajność, które moglibyśmy zaimplementować, programiści stron internetowych powinni pielęgnować adresy URL, a z pomocą HTML5 History API możemy naprawić problemy takie jak powyższy przykład przy odrobinie wysiłku.
Częste wpadki
- Często dobrym pomysłem jest osadzenie lokalizacji żądania Ajaxa w atrybutach
href
elementu zakotwiczenia. - Upewnij się, że
return true
z Javascriptowych manipulatorów kliknięć, gdy ludzie klikają środkiem lub komendą, aby nie nadpisać ich przypadkowo.
Dalsza lektura
- Dokumentacja Mozilli na temat manipulowania historią przeglądarki
- Przykład galerii Ajax z Dive into HTML5
- Wdrożenie pushState przez Twittera
Wsparcie dla przeglądarek
Chrome | Safari | Firefox | Opera | IE | Android | iOS |
---|---|---|---|---|---|---|
31+ | 7.1+ | 34+ | 11.50+ | 10+ | 4.3+ | 7.1+ |