Przejście między dwoma różnymi dokumentami jest nazywane przejściem widoku wielu dokumentów. Dzieje się tak zazwyczaj w przypadku aplikacji wielostronicowych (MPA). Przejścia między dokumentami są obsługiwane w Chrome od wersji Chrome 126.
Obsługa przeglądarek
Przejścia między widokami różnych dokumentów opierają się na tych samych elementach składowych i zasadach co przejścia widoku tego samego dokumentu, co jest bardzo zamierzone:
- Przeglądarka wykonuje migawki elementów z unikalnym atrybutem
view-transition-name
zarówno na starej, jak i nowej stronie. - DOM jest aktualizowany podczas pomijania renderowania.
- I wreszcie przejścia są efektem animacji CSS.
Różnica w porównaniu z przejściami wyświetlania tego samego dokumentu polega na tym, że w przypadku przejścia w widoku różnych dokumentów nie trzeba wywoływać funkcji document.startViewTransition
, aby rozpocząć przejście widoku. Wywołaniem przejścia z jednej strony do drugiej jest przejście z jednej strony na drugą, czyli działanie wykonywane przez użytkownika witryny, który zazwyczaj klika link.
Oznacza to, że nie ma żadnego interfejsu API, który trzeba wywołać, aby rozpocząć przejście między dwoma dokumentami. Należy jednak spełnić 2 warunki:
- Oba dokumenty muszą istnieć w tym samym źródle.
- Na obu stronach trzeba wyrazić zgodę na przeniesienie.
Oba te warunki zostały wyjaśnione w dalszej części tego dokumentu.
Przejścia między dokumentami są ograniczone do nawigacji z tej samej domeny
Przejścia między dokumentami są ograniczone tylko do nawigacji z tej samej domeny. Nawigacja jest uznawana za tę samą pochodzenie, jeśli pochodzenie obu stron uczestniczących w projekcie jest takie samo.
Źródło strony to połączenie użytego schematu, nazwy hosta i portu, zgodnie z informacjami na stronie web.dev.
Możesz na przykład przełączyć widok między dokumentami podczas przechodzenia z developer.chrome.com
do developer.chrome.com/blog
, ponieważ pochodzą one z tej samej domeny.
Tego przejścia nie można dokonać podczas przechodzenia z developer.chrome.com
na www.chrome.com
, ponieważ dotyczy to zasobów z innej domeny i z tej samej witryny.
Przejścia między dokumentami są dobrowolne
Aby możliwe było przełączanie widoku między dwoma dokumentami, obie strony muszą wyrazić na to zgodę. Można to zrobić, korzystając z reguły @view-transition
at w CSS.
W regule @view-transition
ustaw deskryptor navigation
na auto
, aby umożliwić przejścia między widokami dla nawigacji w tej samej domenie w innych dokumentach.
@view-transition {
navigation: auto;
}
Ustawiając deskryptor navigation
na auto
, zezwalasz na przełączanie widoków w przypadku tych elementów NavigationType:
traverse
push
lubreplace
, jeśli aktywacja nie została zainicjowana przez użytkownika za pomocą mechanizmów interfejsu przeglądarki.
Nawigacja wyłączona w usłudze auto
to na przykład nawigacja za pomocą paska adresu URL, klikanie zakładki, a także dowolna forma ponownego załadowania zainicjowanego przez użytkownika lub skrypt.
Jeśli nawigacja trwa zbyt długo (w przypadku Chrome przekracza 4 sekundy), przejście do widoku jest pomijane za pomocą parametru TimeoutError
DOMException
.
Wersja demonstracyjna przejścia między widokami różnych dokumentów
Obejrzyj prezentację narzędzia Stack Navigator, w której wykorzystano przejścia między widokami. Nie ma tu żadnych wywołań funkcji document.startViewTransition()
. Przełączanie widoków jest wywoływane przez przechodzenie z jednej strony na inną.
Dostosowywanie przejść między widokami między dokumentami
Aby dostosować przejścia między widokami różnych dokumentów, możesz skorzystać z pewnych funkcji platformy internetowej.
Funkcje te nie są częścią specyfikacji interfejsu View Transfer API, ale zostały zaprojektowane do używania razem z nimi.
Wydarzenia pageswap
i pagereveal
Aby można było dostosować przejścia między widokami różnych dokumentów, specyfikacja HTML zawiera 2 nowe zdarzenia, których możesz użyć: pageswap
i pagereveal
.
Te 2 zdarzenia są wywoływane przy każdej nawigacji między dokumentami w tej samej domenie niezależnie od tego, czy nastąpi przejście do wyświetlenia. Jeśli nastąpi przeniesienie między tymi 2 stronami, dostęp do obiektu ViewTransition
uzyskasz za pomocą właściwości viewTransition
w tych zdarzeniach.
- Zdarzenie
pageswap
jest wywoływane przed wyrenderowaniem ostatniej klatki strony. Pozwala to wprowadzić w ostatniej chwili zmiany na stronie wychodzącej, tuż przed wykonaniem starych zrzutów. - Zdarzenie
pagereveal
uruchamia się na stronie po tym, jak została zainicjowana lub ponownie aktywowana, ale przed pierwszą możliwością renderowania. Dzięki niemu można dostosować nową stronę przed wykonaniem nowych zrzutów.
Za pomocą tych zdarzeń możesz na przykład szybko ustawić lub zmienić niektóre wartości view-transition-name
albo przekazywać dane z jednego dokumentu do innego, zapisując i odczytując dane z sessionStorage
, aby dostosować przejście widoku przed jego faktycznym uruchomieniem.
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 wydarzeniach.
window.addEventListener("pagereveal", async (e) => {
if (e.viewTransition) {
if (goodReasonToSkipTheViewTransition()) {
e.viewTransition.skipTransition();
}
}
}
Obiekt ViewTransition
w obiekcie pageswap
i pagereveal
to 2 różne obiekty. Obsługują też różne obietnice w różny sposób:
pageswap
: po ukryciu dokumentu stary obiektViewTransition
jest pomijany. W takim przypadkuviewTransition.ready
odrzuca, aviewTransition.finished
zamyka.pagereveal
: obietnicaupdateCallBack
została już zrealizowana. Możesz wykorzystać obietniceviewTransition.ready
iviewTransition.finished
.
Informacje o aktywacji nawigacji
Zarówno w zdarzeniach pageswap
, jak i pagereveal
możesz też podejmować działania na podstawie adresów URL starej i nowej strony.
Na przykład w Nawigatorze stosu MPA typ animacji, który zostanie użyty, zależy od ścieżki nawigacji:
- Podczas przechodzenia ze strony Przegląd na stronę z informacjami nowe treści muszą wsuwać się z prawej do lewej.
- Podczas przechodzenia ze strony z informacjami na stronę Przegląd stara treść musi zostać przesunięta w lewo w prawo.
Aby to zrobić, potrzebujesz informacji o nawigacji, która ma miejsce w przypadku zdarzenia pageswap
lub, w przypadku pagereveal
, właśnie nastąpi.
W tym celu przeglądarki mogą teraz udostępniać obiekty NavigationActivation
, które zawierają informacje o nawigacji po tej samej domenie. Ten obiekt ujawnia używany typ nawigacji oraz bieżące i końcowe wpisy historii miejsc docelowych, jak w narzędziu navigation.entries()
interfejsu Navigation API.
Na aktywowanej stronie możesz uzyskać dostęp do tego obiektu za pomocą usługi navigation.activation
. W wydarzeniu pageswap
masz do niego dostęp za pomocą adresu e.activation
.
Obejrzyj tę prezentację profili, w której wykorzystano informacje NavigationActivation
w zdarzeniach pageswap
i pagereveal
, aby ustawić wartości view-transition-name
w elementach, które muszą uczestniczyć w przejściu widoku.
Dzięki temu nie będzie trzeba dekorować każdego produktu z listy za pomocą z góry view-transition-name
. Dzięki JavaScriptowi od razu dzieje się to tylko w elementach, które go potrzebują.
Kod wygląda następująco:
// 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 czyści się też po sobie, usuwając wartości view-transition-name
po uruchomieniu przejścia. W ten sposób strona jest gotowa do kolejnych nawigacji i umożliwia poruszanie się po historii.
Aby Ci w tym pomóc, użyj tej funkcji narzędzia, która tymczasowo ustawia 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 teraz 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);
}
}
});
Oczekiwanie na wczytanie treści z blokowaniem renderowania
Obsługa przeglądarek
W niektórych przypadkach możesz chcieć wstrzymać pierwsze wyrenderowanie strony do czasu, aż dany element znajdzie się w nowym DOM. Pozwala to uniknąć migania i zapewnia stabilny stan animacji.
W elemencie <head>
zdefiniuj co najmniej 1 identyfikator elementu, który musi być widoczny przed pierwszym renderowaniem strony. Aby to zrobić, użyj poniższego metatagu.
<link rel="expect" blocking="render" href="#section1">
Ten metatag oznacza, że element powinien być obecny w modelu DOM, a nie ładować treści. Na przykład w przypadku obrazów wystarczy obecność tagu <img>
z parametrem id
w drzewie DOM, aby warunek został spełniony. Obraz może wciąż być wczytywany.
Zanim skupisz się na blokowaniu renderowania, pamiętaj, że renderowanie przyrostowe to podstawowy aspekt internetu, więc jeśli chcesz je blokować, zachowaj rozwagę. Wpływ renderowania blokującego trzeba ocenić indywidualnie. Domyślnie unikaj używania narzędzia blocking=render
, chyba że możesz aktywnie zmierzyć i zmierzyć wpływ tej zmiany na użytkowników, mierząc jej wpływ na podstawowe wskaźniki internetowe.
Wyświetlanie typów przejść podczas przejść w widoku różnych dokumentów
Przejścia widoku różnych dokumentów obsługują też typy przejść widoku, co pozwala dostosować animacje i przechwytywane elementy.
Gdy na przykład przechodzisz na następną lub poprzednią stronę w podziale na strony, możesz użyć różnych animacji w zależności od tego, czy przejdziesz na wyższą czy na niższą stronę w sekwencji.
Aby od razu ustawić te typy, dodaj je w regule @view-transition
:
@view-transition {
navigation: auto;
types: slide, forwards;
}
Aby na bieżąco określać ich typy, używaj zdarzeń pageswap
i pagereveal
do modyfikowania 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. Musisz określić, których typów użyjesz na co najmniej nowej stronie, by animacje działały zgodnie z oczekiwaniami.
Aby zareagować na te typy, użyj selektora pseudoklasy :active-view-transition-type()
w taki sam sposób jak przy przejściach widoku 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;
}
}
Typy mają zastosowanie tylko do przejść w widoku aktywnym, więc są automatycznie czyszczone po zakończeniu przejścia w widoku. Z tego powodu typy dobrze współpracują z takimi funkcjami jak BFCache.
Prezentacja
W tej prezentacji o podziale na strony zawartość strony przesuwa się do przodu lub do tyłu w zależności od otwieranego numeru strony.
Typ przejścia jest określany w zdarzeniach pagereveal
i pageswap
na podstawie adresów URL prowadzących do i z powrotem.
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ę
Opinie deweloperów są dla nas bardzo ważne. Aby udostępnić ten plik, zgłoś problem zespołowi roboczej ds. usług CSS na GitHubie, przesyłając sugestie i pytania. Dodaj do problemu prefiks [css-view-transitions]
.
Jeśli napotkasz błąd, zgłoś błąd w Chromium.