Poprawa przepływu użytkowników przez przejścia między stronami
Opublikowany: 2022-03-10Przejścia między stronami mogą poprawić wrażenia, zachowując (lub nawet poprawiając) kontekst użytkownika, utrzymując jego uwagę oraz zapewniając ciągłość wizualną i pozytywne opinie. Jednocześnie przejścia między stronami mogą być również estetyczne i przyjemne, a dobrze wykonane mogą wzmocnić markę.

W tym artykule krok po kroku stworzymy przejście między stronami. Porozmawiamy również o zaletach i wadach tej techniki oraz o tym, jak wykorzystać ją do granic możliwości.
Przykłady
Wiele aplikacji mobilnych dobrze wykorzystuje przejścia między widokami. W poniższym przykładzie, zgodnym z wytycznymi Google dotyczącymi projektowania materiałów, widzimy, jak animacja przekazuje hierarchiczne i przestrzenne relacje między stronami.
Dlaczego nie stosujemy tego samego podejścia w przypadku naszych stron internetowych? Dlaczego nie przeszkadza nam to, że użytkownik czuje, jakby był teleportowany za każdym razem, gdy zmienia się strona?
Jak przechodzić między stronami internetowymi
Ramy SPA
Zanim pobrudzimy sobie ręce, powinienem powiedzieć coś o frameworkach aplikacji jednostronicowych (SPA). Jeśli używasz frameworka SPA (takiego jak AngularJS, Backbone.js lub Ember), tworzenie przejść między stronami będzie znacznie łatwiejsze, ponieważ cały routing jest już obsługiwany przez JavaScript. Zapoznaj się z odpowiednią dokumentacją, aby zobaczyć, jak przenosić strony przy użyciu wybranego przez Ciebie frameworka, ponieważ prawdopodobnie istnieje kilka dobrych przykładów i samouczków.
Zły kierunek
Moja pierwsza próba stworzenia przejścia między stronami wyglądała mniej więcej tak:
document.addEventListener('DOMContentLoaded', function() { // Animate in }); document.addEventListener('beforeunload', function() { // Animate out });
Koncepcja jest prosta: użyj jednej animacji, gdy użytkownik opuszcza stronę, a innej, gdy wczytuje się nowa strona.
Jednak szybko odkryłem, że to rozwiązanie ma pewne ograniczenia:
- Nie wiemy, ile czasu zajmie wczytanie następnej strony, więc animacja może nie wyglądać płynnie.
- Nie możemy tworzyć przejść łączących zawartość z poprzedniej i następnej strony.
W rzeczywistości jedynym sposobem na osiągnięcie płynnego i płynnego przejścia jest posiadanie pełnej kontroli nad procesem zmiany strony, a zatem nie zmienianie strony w ogóle . Dlatego musimy zmienić nasze podejście do problemu.
Właściwy sposób
Przyjrzyjmy się krokom związanym z tworzeniem prostego przejścia między stronami we właściwy sposób. Obejmuje to coś, co nazywa pushState
AJAX (lub PJAX), co zasadniczo zmieni naszą witrynę internetową w rodzaj witryny jednostronicowej.
Ta technika nie tylko zapewnia płynne i przyjemne przejścia, ale także skorzystamy z innych zalet, które omówimy szczegółowo w dalszej części tego artykułu.
Zapobiegaj domyślnemu zachowaniu łącza
Pierwszym krokiem jest utworzenie detektora zdarzeń click
dla wszystkich linków, który uniemożliwi przeglądarce wykonanie domyślnego zachowania i dostosowanie sposobu obsługi zmian na stronie.
// Note, we are purposely binding our listener on the document object // so that we can intercept any anchors added in future. document.addEventListener('click', function(e) { var el = e.target; // Go up in the nodelist until we find a node with .href (HTMLAnchorElement) while (el && !el.href) { el = el.parentNode; } if (el) { e.preventDefault(); return; } });
Ta metoda dodawania detektora zdarzeń do elementu nadrzędnego, zamiast dodawania go do każdego konkretnego węzła, nazywana jest delegowaniem zdarzeń i jest możliwa ze względu na charakter pęcherzyków zdarzeń interfejsu API HTML DOM.
Pobierz stronę
Teraz, gdy przerwaliśmy działanie przeglądarki, gdy próbuje zmienić stronę, możemy ręcznie pobrać tę stronę za pomocą interfejsu API Fetch. Przyjrzyjmy się następującej funkcji, która pobiera zawartość HTML strony po podaniu jej adresu URL.
function loadPage(url) { return fetch(url, { method: 'GET' }).then(function(response) { return response.text(); }); }
W przypadku przeglądarek, które nie obsługują interfejsu Fetch API, rozważ dodanie wypełnienia lub użycie staromodnego, dobrego XMLHttpRequest
.
Zmień bieżący adres URL
HTML5 ma fantastyczny interfejs API o nazwie pushState
, który umożliwia stronom internetowym dostęp i modyfikowanie historii przeglądarki bez ładowania żadnych stron. Poniżej używamy go do zmiany bieżącego adresu URL na adres URL następnej strony. Zwróć uwagę, że jest to modyfikacja naszego wcześniej zadeklarowanego modułu obsługi zdarzeń kliknięcia kotwicy.
if (el) { e.preventDefault(); history.pushState(null, null, el.href); changePage(); return; }
Jak mogłeś zauważyć, dodaliśmy również wywołanie funkcji o nazwie changePage
, której szczegółowo przyjrzymy się wkrótce. Ta sama funkcja zostanie również wywołana w zdarzeniu popstate
, które jest uruchamiane, gdy zmieni się aktywny wpis historii przeglądarki (np. gdy użytkownik kliknie przycisk Wstecz w przeglądarce):
window.addEventListener('popstate', changePage);
Z tym wszystkim w zasadzie budujemy bardzo prymitywny system routingu, w którym mamy tryby aktywne i pasywne.
Nasz tryb aktywny jest używany, gdy użytkownik kliknie link i zmienimy adres URL za pomocą pushState
, natomiast tryb pasywny jest używany, gdy zmieni się adres URL i otrzymamy powiadomienie o zdarzeniu popstate
. W obu przypadkach changePage
, który zajmie się odczytaniem nowego adresu URL i załadowaniem odpowiedniej strony.
Przeanalizuj i dodaj nową zawartość
Zazwyczaj przeglądane strony mają wspólne elementy, takie jak header
i footer
. Załóżmy, że używamy następującej struktury DOM na wszystkich naszych stronach (która w rzeczywistości jest strukturą samego Smashing Magazine):
var main = document.querySelector('main'); function changePage() { // Note, the URL has already been changed var url = window.location.href; loadPage(url).then(function(responseText) { var wrapper = document.createElement('div'); wrapper.innerHTML = responseText; var oldContent = document.querySelector('.cc'); var newContent = wrapper.querySelector('.cc'); main.appendChild(newContent); animate(oldContent, newContent); }); }
Animować!
Gdy użytkownik kliknie łącze, funkcja changePage
pobiera kod HTML tej strony, a następnie wyodrębnia kontener cc
i dodaje go do main
elementu. W tym momencie na naszej stronie mamy dwa kontenery cc
, pierwszy należy do poprzedniej strony, a drugi z następnej strony.

Kolejna funkcja, animate
, zajmuje się przenikaniem dwóch kontenerów poprzez nakładanie się na nie, zanikanie starego, zanikanie w nowym i usuwanie starego. W tym przykładzie używam interfejsu Web Animations API do tworzenia animacji zanikania, ale oczywiście możesz użyć dowolnej techniki lub biblioteki, którą chcesz.
function animate(oldContent, newContent) { oldContent.style.position = 'absolute'; var fadeOut = oldContent.animate({ opacity: [1, 0] }, 1000); var fadeIn = newContent.animate({ opacity: [0, 1] }, 1000); fadeIn.onfinish = function() { oldContent.parentNode.removeChild(oldContent); }; }
Ostateczny kod jest dostępny na GitHub.
A to są podstawy przenoszenia stron internetowych!
Zastrzeżenia i ograniczenia
Mały przykład, który właśnie stworzyliśmy, jest daleki od ideału. W rzeczywistości wciąż nie wzięliśmy pod uwagę kilku rzeczy:
- Upewnij się, że wpływamy na prawidłowe linki.
Przed zmianą zachowania linku powinniśmy dodać znacznik wyboru, aby upewnić się, że powinien zostać zmieniony. Na przykład, powinniśmy zignorować wszystkie linki ztarget="_blank"
(co otwiera stronę w nowej karcie), wszystkie linki do zewnętrznych domen i kilka innych specjalnych przypadków, takich jakControl/Command + click
(co powoduje również otwarcie strony w nową kartę). - Zaktualizuj elementy poza głównym kontenerem treści.
Obecnie, gdy zmienia się strona, wszystkie elementy poza konteneremcc
pozostają takie same. Jednak niektóre z tych elementów musiałyby zostać zmienione (co teraz można było zrobić tylko ręcznie), w tymtitle
dokumentu, element menu zactive
klasą i potencjalnie wiele innych w zależności od witryny. - Zarządzaj cyklem życia JavaScript.
Nasza strona zachowuje się teraz jak SPA, w którym przeglądarka sama nie zmienia stron. Dlatego musimy ręcznie zająć się cyklem życia JavaScript — na przykład wiązaniem i odłączaniem niektórych zdarzeń, ponowną oceną wtyczek oraz dołączaniem wypełniaczy i kodu stron trzecich.
Obsługa przeglądarki
Jedynym wymaganiem dla tego trybu nawigacji, który wdrażamy, jest interfejs API pushState
, który jest dostępny we wszystkich nowoczesnych przeglądarkach. Ta technika działa w pełni jako progresywne ulepszenie . Strony są nadal obsługiwane i dostępne w zwykły sposób, a witryna będzie nadal działać normalnie, gdy JavaScript jest wyłączony.
Jeśli używasz frameworka SPA, rozważ użycie nawigacji PJAX, aby nawigacja była szybka. W ten sposób zyskujesz starsze wsparcie i tworzysz witrynę bardziej przyjazną SEO.
Idąc jeszcze dalej
Możemy nadal przesuwać granice tej techniki, optymalizując jej niektóre aspekty. Kilka następnych sztuczek przyspieszy nawigację, znacznie poprawiając wrażenia użytkownika.
Korzystanie z pamięci podręcznej
Zmieniając nieco naszą funkcję loadPage
, możemy dodać prostą pamięć podręczną, która zapewnia, że odwiedzone już strony nie zostaną ponownie załadowane.
var cache = {}; function loadPage(url) { if (cache[url]) { return new Promise(function(resolve) { resolve(cache[url]); }); } return fetch(url, { method: 'GET' }).then(function(response) { cache[url] = response.text(); return cache[url]; }); }
Jak można się domyślić, możemy użyć bardziej trwałej pamięci podręcznej z Cache API lub innej pamięci podręcznej trwałego przechowywania po stronie klienta (takiej jak IndexedDB).
Animowanie bieżącej strony
Nasz efekt przenikania wymaga, aby następna strona została załadowana i gotowa przed zakończeniem przejścia. Z innym efektem możemy zacząć animować starą stronę, gdy tylko użytkownik kliknie łącze, co dałoby użytkownikowi natychmiastową informację zwrotną, co jest doskonałą pomocą w postrzeganiu wydajności.
Korzystając z obietnic, radzenie sobie z tego rodzaju sytuacją staje się bardzo łatwe. Metoda .all
tworzy nową obietnicę, która zostaje rozwiązana, gdy tylko wszystkie obietnice uwzględnione jako argumenty zostaną rozwiązane.
// As soon as animateOut() and loadPage() are resolved… Promise.all[animateOut(), loadPage(url)] .then(function(values) { …
Wstępne pobieranie następnej strony
Korzystając tylko z nawigacji PJAX, zmiany strony są zwykle prawie dwa razy szybsze niż domyślna nawigacja, ponieważ przeglądarka nie musi analizować i oceniać żadnych skryptów ani stylów na nowej stronie.
Możemy jednak pójść jeszcze dalej, rozpoczynając wstępne ładowanie następnej strony, gdy użytkownik najedzie kursorem na link lub zacznie go dotykać.
Jak widać, opóźnienie w najechaniu kursorem i kliknięciu przez użytkownika wynosi zwykle od 200 do 300 milisekund. Jest to czas martwy i zwykle wystarcza na załadowanie kolejnej strony.
Biorąc to pod uwagę, mądrze pobieraj z wyprzedzeniem, ponieważ może łatwo stać się wąskim gardłem. Na przykład, jeśli masz długą listę linków, a użytkownik ją przewija, ta technika wstępnie pobierze wszystkie strony, ponieważ linki przechodzą pod myszą.
Innym czynnikiem, który możemy wykryć i wziąć pod uwagę przy podejmowaniu decyzji o pobieraniu z wyprzedzeniem, jest szybkość połączenia użytkownika. (Być może będzie to możliwe w przyszłości dzięki interfejsowi Network Information API).
Częściowe wyjście
W naszej funkcji loadPage
pobieramy cały dokument HTML, ale w rzeczywistości potrzebujemy tylko kontenera cc
. Jeśli używamy języka po stronie serwera, możemy wykryć, czy żądanie pochodzi z określonego niestandardowego wywołania AJAX, a jeśli tak, wyprowadzić tylko potrzebny kontener. Korzystając z interfejsu API Headers, możemy wysłać niestandardowy nagłówek HTTP w naszym żądaniu pobrania.
function loadPage(url) { var myHeaders = new Headers(); myHeaders.append('x-pjax', 'yes'); return fetch(url, { method: 'GET', headers: myHeaders, }).then(function(response) { return response.text(); }); }
Następnie po stronie serwera (w tym przypadku używając PHP) możemy wykryć, czy nasz niestandardowy nagłówek istnieje przed wysłaniem tylko wymaganego kontenera:
if (isset($_SERVER['HTTP_X_PJAX'])) { // Output just the container }
Zmniejszy to rozmiar wiadomości HTTP, a także zmniejszy obciążenie po stronie serwera.
Zawijanie
Po wdrożeniu tej techniki w kilku projektach zdałem sobie sprawę, że biblioteka wielokrotnego użytku byłaby niezwykle pomocna. Za każdym razem oszczędziłoby mi to czasu przy wdrażaniu tego rozwiązania, pozwalając mi skupić się na samych efektach przejścia.
W ten sposób narodził się Barba.js, mała biblioteka (zminimalizowana 4 KB i gZip'd), która oddziela całą tę złożoność i zapewnia ładne, czyste i proste API dla programistów. Uwzględnia również widoki i zawiera przejścia wielokrotnego użytku, buforowanie, pobieranie z wyprzedzeniem i zdarzenia. Jest open source i jest dostępny na GitHub.
Wniosek
Zobaczyliśmy teraz, jak stworzyć efekt crossfade oraz zalety i wady korzystania z nawigacji PJAX, aby skutecznie przekształcić naszą stronę internetową w SPA. Oprócz korzyści płynących z samego przejścia, widzieliśmy również, jak zaimplementować proste mechanizmy buforowania i wstępnego pobierania, aby przyspieszyć ładowanie nowych stron.
Cały ten artykuł jest oparty na moim osobistym doświadczeniu i tym, czego nauczyłem się podczas wdrażania przejść między stronami w projektach, nad którymi pracowałem. Jeśli masz jakieś pytania, nie wahaj się zostawić komentarza lub skontaktuj się ze mną na Twitterze — moje dane są poniżej!
Dalsze czytanie na SmashingMag:
- Inteligentne przejścia w projektowaniu doświadczeń użytkownika
- Projektowanie w przejściu do świata wielu urządzeń
- Zapewnianie natywnego doświadczenia dzięki technologiom internetowym