Przejście między widokami w 2 różnych dokumentach nazywa się przejściem między widokami dokumentów. Zwykle tak się dzieje w przypadku aplikacji wielostronicowych. Przejścia między widokami dokumentów są obsługiwane w Chrome od wersji 126.
Obsługa przeglądarek
Przejścia między widokami dokumentów korzystają z tych samych elementów i zasad co przejścia między widokami tego samego dokumentu. Jest to celowe działanie:
- Przeglądarka wykonuje migawki elementów, które mają unikalne wartości
view-transition-name
zarówno na starej, jak i nowej stronie. - DOM jest aktualizowany, gdy renderowanie jest blokowane.
- I na koniec przejścia są obsługiwane przez animacje CSS.
Różnica w stosunku do przejść widoku w tym samym dokumencie polega na tym, że w przypadku przejść widoku między dokumentami nie trzeba wywoływać funkcji document.startViewTransition
, aby rozpocząć przejście widoku. Zamiast tego przejście między widokami w różnych dokumentach jest wywoływane przez nawigację w ramach tego samego źródła z jednej strony na drugą. Jest to działanie, które użytkownik Twojej witryny wykonuje, klikając link.
Innymi słowy, nie ma interfejsu API, który można wywołać, aby rozpocząć przejście między widokami 2 dokumentów. Musisz jednak spełnić 2 warunki:
- Oba dokumenty muszą pochodzić z tego samego źródła.
- Aby umożliwić przejście do widoku, musisz wyrazić zgodę na wyświetlanie na obu stronach.
Oba te warunki są opisane w dalszej części tego dokumentu.
Przejścia między widokami dokumentów są ograniczone do nawigacji w ramach tej samej domeny.
Przejścia między widokami dokumentów są ograniczone tylko do przekierowań w ramach tej samej domeny. Nawigacja jest uznawana za pochodzącą z tej samej domeny, jeśli domena obu stron jest taka sama.
Źródło strony to połączenie schematu, nazwy hosta i portu, jak opisano na stronie web.dev.
Możesz na przykład przełączać widoki w dokumentach podczas przechodzenia z developer.chrome.com
do developer.chrome.com/blog
, ponieważ te dokumenty mają ten sam element źródła.
Nie możesz przejść z poziomu developer.chrome.com
do www.chrome.com
, ponieważ są to różne źródła i ta sama strona.
Przejścia między dokumentami wymagają zgody
Aby umożliwić przejście między dokumentami, obie strony muszą wyrazić na to zgodę. Służy do tego reguła at @view-transition
w CSS.
W atrybucie @view-transition
ustaw wartość navigation
na auto
, aby umożliwić przejścia między widokami w przypadku nawigacji w dokumentach w ramach tej samej domeny.
@view-transition {
navigation: auto;
}
Ustawiając opis navigation
na auto
, zezwalasz na przejścia między widokami w przypadku tych typów NavigationType:
traverse
push
lubreplace
, jeśli aktywacja nie została zainicjowana przez użytkownika za pomocą mechanizmów interfejsu przeglądarki.
Nawigacje wykluczone z auto
to na przykład nawigacja za pomocą paska adresu URL lub kliknięcie zakładki, a także każda forma odświeżania inicjowana przez użytkownika lub skrypt.
Jeśli nawigacja trwa zbyt długo (w przypadku Chrome – ponad 4 sekundy), przejście między widokami jest pomijane i zastępowane przez TimeoutError
DOMException
.
Demonstracja przełączania widoków w dokumentach
Zapoznaj się z tym demonstracyjnym filmem, w którym przejścia między widokami służą do tworzenia demonstracji nawigacji w Stacku. Nie ma tu wywołań funkcji document.startViewTransition()
, przejścia między widokami są wywoływane przez przechodzenie z jednej strony na drugą.
Dostosowywanie przejść między widokami dokumentów
Aby dostosować przejścia między widokami dokumentów, możesz użyć niektórych funkcji platformy internetowej.
Te funkcje nie są częścią specyfikacji interfejsu ViewTransition API, ale są przeznaczone do użytku w połączeniu z nim.
Zdarzenia pageswap
i pagereveal
Aby umożliwić dostosowywanie przejść między widokami dokumentów, specyfikacja HTML zawiera 2 nowe zdarzenia, których możesz użyć: pageswap
i pagereveal
.
Te 2 zdarzenia są wywoływane w przypadku każdej nawigacji między dokumentami w ramach tego samego źródła niezależnie od tego, czy nastąpi przejście do widoku. Jeśli przejście między widokami ma nastąpić na 2 stronach, możesz uzyskać dostęp do obiektu ViewTransition
, używając właściwości viewTransition
w tych zdarzeniach.
- Zdarzenie
pageswap
jest wywoływane przed wyświetleniem ostatniego obrazu strony. Możesz z niego skorzystać, aby w ostatniej chwili wprowadzić zmiany na stronie wychodzącej tuż przed utworzeniem starych zrzutów ekranu. - Zdarzenie
pagereveal
jest wywoływane na stronie po jej zainicjowaniu lub ponownym zainicjowaniu, ale przed pierwszą możliwością renderowania. Dzięki temu możesz dostosować nową stronę przed utworzeniem nowych migawek.
Możesz na przykład używać tych zdarzeń, aby szybko ustawiać lub zmieniać niektóre wartości view-transition-name
lub przekazywać dane z jednego dokumentu do drugiego, zapisując i czytając dane z sessionStorage
, aby dostosować przejście między widokami przed jego wykonaniem.
let lastClickX, lastClickY;
document.addEventListener('click', (event) => {
if (event.target.tagName.toLowerCase() === 'a') return;
lastClickX = event.clientX;
lastClickY = event.clientY;
});
// Write position to storage on old page
window.addEventListener('pageswap', (event) => {
if (event.viewTransition && lastClick) {
sessionStorage.setItem('lastClickX', lastClickX);
sessionStorage.setItem('lastClickY', lastClickY);
}
});
// Read position from storage on new page
window.addEventListener('pagereveal', (event) => {
if (event.viewTransition) {
lastClickX = sessionStorage.getItem('lastClickX');
lastClickY = sessionStorage.getItem('lastClickY');
}
});
Jeśli chcesz, możesz pominąć przejście w obu zdarzeniach.
window.addEventListener("pagereveal", async (e) => {
if (e.viewTransition) {
if (goodReasonToSkipTheViewTransition()) {
e.viewTransition.skipTransition();
}
}
}
Obiekt ViewTransition
w obiektach pageswap
i pagereveal
to 2 różne obiekty. Różne obietnice są też traktowane inaczej:
pageswap
: gdy dokument jest ukryty, stary obiektViewTransition
jest pomijany. W takim przypadkuviewTransition.ready
odrzuca, aviewTransition.finished
rozwiązuje.pagereveal
:updateCallBack
obietnica została już spełniona. Możesz skorzystać z obietnicviewTransition.ready
iviewTransition.finished
.
Informacje o aktywacji nawigacji
W przypadku zdarzeń pageswap
i pagereveal
możesz też podjąć działanie na podstawie adresów URL starych i nowych stron.
Na przykład w Nawigatorze architektury MPA typ animacji zależy od ścieżki nawigacji:
- Gdy przechodzisz ze strony Przegląd na stronę Szczegóły, nowe treści muszą przesuwać się od prawej do lewej.
- Gdy przechodzisz ze strony z informacjami na stronę z przeglądem, stara zawartość powinna przesunąć się z poziomu lewego do prawego.
Aby to zrobić, potrzebujesz informacji o nawigacji, która w przypadku pageswap
ma się dopiero rozpocząć, a w przypadku pagereveal
właśnie się zakończyła.
W tym celu przeglądarki mogą teraz udostępniać obiekty NavigationActivation
, które zawierają informacje o nawigacji w ramach tego samego źródła. Obiekt ten udostępnia użyty typ nawigacji, bieżące i ostateczne wpisy historii miejsca docelowego, które można znaleźć w navigation.entries()
z interfejsu Navigation API.
Na stronie aktywowanej możesz uzyskać dostęp do tego obiektu za pomocą elementu navigation.activation
. W przypadku pageswap
możesz uzyskać do niego dostęp za pomocą e.activation
.
Obejrzyj demo funkcji Profile, która używa informacji NavigationActivation
w zdarzeniach pageswap
i pagereveal
do ustawiania wartości view-transition-name
w elementach, które muszą uczestniczyć w przechodzeniu między widokami.
Dzięki temu nie musisz ozdabiać każdego elementu na liście za pomocą znaku view-transition-name
. Zamiast tego, aby stosować JavaScript, używamy go tylko w przypadku elementów, które tego wymagają.
Kod wygląda tak:
// OLD PAGE LOGIC
window.addEventListener('pageswap', async (e) => {
if (e.viewTransition) {
const targetUrl = new URL(e.activation.entry.url);
// Navigating to a profile page
if (isProfilePage(targetUrl)) {
const profile = extractProfileNameFromUrl(targetUrl);
// Set view-transition-name values on the clicked row
document.querySelector(`#${profile} span`).style.viewTransitionName = 'name';
document.querySelector(`#${profile} img`).style.viewTransitionName = 'avatar';
// Remove view-transition-names after snapshots have been taken
// (this to deal with BFCache)
await e.viewTransition.finished;
document.querySelector(`#${profile} span`).style.viewTransitionName = 'none';
document.querySelector(`#${profile} img`).style.viewTransitionName = 'none';
}
}
});
// NEW PAGE LOGIC
window.addEventListener('pagereveal', async (e) => {
if (e.viewTransition) {
const fromURL = new URL(navigation.activation.from.url);
const currentURL = new URL(navigation.activation.entry.url);
// Navigating from a profile page back to the homepage
if (isProfilePage(fromURL) && isHomePage(currentURL)) {
const profile = extractProfileNameFromUrl(currentURL);
// Set view-transition-name values on the elements in the list
document.querySelector(`#${profile} span`).style.viewTransitionName = 'name';
document.querySelector(`#${profile} img`).style.viewTransitionName = 'avatar';
// Remove names after snapshots have been taken
// so that we're ready for the next navigation
await e.viewTransition.ready;
document.querySelector(`#${profile} span`).style.viewTransitionName = 'none';
document.querySelector(`#${profile} img`).style.viewTransitionName = 'none';
}
}
});
Kod sam się też oczyszcza, usuwając wartości view-transition-name
po przejściu widoku. W ten sposób strona jest gotowa do kolejnych przejść i może też obsługiwać przechodzenie przez historię.
Aby ułatwić sobie pracę, użyj tej funkcji pomocniczej, która tymczasowo ustawia zmienne view-transition-name
.
const setTemporaryViewTransitionNames = async (entries, vtPromise) => {
for (const [$el, name] of entries) {
$el.style.viewTransitionName = name;
}
await vtPromise;
for (const [$el, name] of entries) {
$el.style.viewTransitionName = '';
}
}
Poprzedni kod można uprościć w ten sposób:
// OLD PAGE LOGIC
window.addEventListener('pageswap', async (e) => {
if (e.viewTransition) {
const targetUrl = new URL(e.activation.entry.url);
// Navigating to a profile page
if (isProfilePage(targetUrl)) {
const profile = extractProfileNameFromUrl(targetUrl);
// Set view-transition-name values on the clicked row
// Clean up after the page got replaced
setTemporaryViewTransitionNames([
[document.querySelector(`#${profile} span`), 'name'],
[document.querySelector(`#${profile} img`), 'avatar'],
], e.viewTransition.finished);
}
}
});
// NEW PAGE LOGIC
window.addEventListener('pagereveal', async (e) => {
if (e.viewTransition) {
const fromURL = new URL(navigation.activation.from.url);
const currentURL = new URL(navigation.activation.entry.url);
// Navigating from a profile page back to the homepage
if (isProfilePage(fromURL) && isHomePage(currentURL)) {
const profile = extractProfileNameFromUrl(currentURL);
// Set view-transition-name values on the elements in the list
// Clean up after the snapshots have been taken
setTemporaryViewTransitionNames([
[document.querySelector(`#${profile} span`), 'name'],
[document.querySelector(`#${profile} img`), 'avatar'],
], e.viewTransition.ready);
}
}
});
Czekaj na wczytanie treści z blokowaniem renderowania
Obsługa przeglądarek
W niektórych przypadkach możesz chcieć wstrzymać się z pierwszym wyrenderowaniem strony, dopóki w nowym DOM nie pojawi się określony element. Zapobiega to miganiu i zapewnia stabilność stanu, który animujesz.
W tagu <head>
za pomocą tego metatagu zdefiniuj co najmniej 1 identyfikator elementu, który musi być obecny przed pierwszym wyrenderowaniem strony.
<link rel="expect" blocking="render" href="#section1">
Ten tag meta oznacza, że element powinien znajdować się w DOM, a nie że zawartość powinna być wczytana. Na przykład w przypadku obrazów wystarczy, że w drzewie DOM znajdzie się tag <img>
z określonym atrybutem id
, aby warunek zwrócił wartość true (prawda). Obraz może się nadal wczytywać.
Zanim całkowicie zablokujesz renderowanie, pamiętaj, że renderowanie stopniowe jest podstawowym aspektem sieci, dlatego zachowaj ostrożność, gdy decydujesz się na blokowanie renderowania. Wpływ blokowania renderowania należy oceniać indywidualnie w każdej sytuacji. Domyślnie unikaj używania blocking=render
, chyba że możesz aktywnie mierzyć i ocenić wpływ tej funkcji na użytkowników, mierząc wpływ na podstawowe wskaźniki internetowe.
Wyświetlanie typów przejść w przejściach między dokumentami
Przejścia między dokumentami obsługują też typy przejść, które umożliwiają dostosowywanie animacji i określanie, które elementy mają być rejestrowane.
Na przykład podczas przechodzenia do następnej lub poprzedniej strony w sekwencji możesz użyć różnych animacji w zależności od tego, czy przechodzisz do strony wyższej, czy niższej w sekwencji.
Aby ustawić te typy z wyprzedzeniem, dodaj je w regułach at-rule:@view-transition
@view-transition {
navigation: auto;
types: slide, forwards;
}
Aby ustawiać typy w biegu działania, użyj zdarzeń pageswap
i pagereveal
do manipulowania wartością parametru e.viewTransition.types
.
window.addEventListener("pagereveal", async (e) => {
if (e.viewTransition) {
const transitionType = determineTransitionType(navigation.activation.from, navigation.activation.entry);
e.viewTransition.types.add(transitionType);
}
});
Typy nie są automatycznie przenoszone z obiektu ViewTransition
na starej stronie do obiektu ViewTransition
na nowej stronie. Aby animacje działały zgodnie z oczekiwaniami, musisz określić typy, których chcesz użyć na co najmniej nowej stronie.
Aby odpowiedzieć na te typy, użyj selektora pseudoklasy :active-view-transition-type()
w taki sam sposób jak w przypadku przejść w ramach tego samego dokumentu.
/* Determine what gets captured when the type is forwards or backwards */
html:active-view-transition-type(forwards, backwards) {
:root {
view-transition-name: none;
}
article {
view-transition-name: content;
}
.pagination {
view-transition-name: pagination;
}
}
/* Animation styles for forwards type only */
html:active-view-transition-type(forwards) {
&::view-transition-old(content) {
animation-name: slide-out-to-left;
}
&::view-transition-new(content) {
animation-name: slide-in-from-right;
}
}
/* Animation styles for backwards type only */
html:active-view-transition-type(backwards) {
&::view-transition-old(content) {
animation-name: slide-out-to-right;
}
&::view-transition-new(content) {
animation-name: slide-in-from-left;
}
}
/* Animation styles for reload type only */
html:active-view-transition-type(reload) {
&::view-transition-old(root) {
animation-name: fade-out, scale-down;
}
&::view-transition-new(root) {
animation-delay: 0.25s;
animation-name: fade-in, scale-up;
}
}
Ponieważ typy dotyczą tylko aktywnego przejścia widoku, są automatycznie usuwane po zakończeniu przejścia. Dzięki temu typy dobrze współpracują z funkcjami takimi jak BFCache.
Prezentacja
W tym demo podziału na strony zawartość strony przesuwa się do przodu lub do tyłu w zależności od numeru strony, do której się przemieszczasz.
Typ przejścia do użycia jest określany w zdarzeniach pagereveal
i pageswap
na podstawie adresów URL „do” i „z”.
const determineTransitionType = (fromNavigationEntry, toNavigationEntry) => {
const currentURL = new URL(fromNavigationEntry.url);
const destinationURL = new URL(toNavigationEntry.url);
const currentPathname = currentURL.pathname;
const destinationPathname = destinationURL.pathname;
if (currentPathname === destinationPathname) {
return "reload";
} else {
const currentPageIndex = extractPageIndexFromPath(currentPathname);
const destinationPageIndex = extractPageIndexFromPath(destinationPathname);
if (currentPageIndex > destinationPageIndex) {
return 'backwards';
}
if (currentPageIndex < destinationPageIndex) {
return 'forwards';
}
return 'unknown';
}
};
Prześlij opinię
Zawsze chętnie przyjmujemy opinie deweloperów. Aby podzielić się opinią, prześlij zgłoszenie do grupy roboczej ds. usług porównywania cen na GitHub, dodając sugestie i pytania. Dodaj do problemu prefiks [css-view-transitions]
.
Jeśli napotkasz błąd, zgłoś go w Chromium.