Gdy przejście widoku następuje między 2 różnymi dokumentami, nazywa się je przejściem widoku między dokumentami. Zwykle tak jest w przypadku aplikacji wielostronicowych. Przejścia między widokami w różnych dokumentach są obsługiwane w Chrome od wersji 126.
Przejścia między dokumentami opierają się na tych samych elementach i zasadach co przejścia w ramach tego samego dokumentu, co jest celowe:
- Przeglądarka robi migawki elementów, które mają unikalny atrybut
view-transition-namezarówno na starej, jak i na nowej stronie. - DOM jest aktualizowany, gdy renderowanie jest wstrzymane.
- Przejścia są oparte na animacjach CSS.
W porównaniu z przejściami widoku w tym samym dokumencie w przypadku przejść widoku między dokumentami nie musisz wywoływać funkcji document.startViewTransition, aby rozpocząć przejście widoku. Zamiast tego regułą przejścia widoku między dokumentami jest nawigacja w ramach tej samej domeny z jednej strony na drugą, czyli działanie, które użytkownik witryny zwykle wykonuje, klikając link.
Innymi słowy, nie ma interfejsu API, którego można użyć do rozpoczęcia przejścia widoku między dwoma dokumentami. Muszą jednak zostać spełnione 2 warunki:
- Oba dokumenty muszą znajdować się w tym samym źródle.
- Obie strony muszą wyrazić zgodę na przejście widoku.
Oba te warunki wyjaśniamy w dalszej części tego dokumentu.
Przejścia między widokami dokumentów są ograniczone do nawigacji w ramach tej samej domeny.
Przejścia widoku między dokumentami są ograniczone tylko do nawigacji w ramach tej samej domeny. Nawigacja jest uznawana za pochodzącą z tej samej domeny, jeśli domeny obu stron biorących w niej udział są takie same.
Źródło strony to połączenie użytego schematu, nazwy hosta i portu, jak opisano na web.dev.
Możesz na przykład mieć przejście widoku między dokumentami podczas przechodzenia z developer.chrome.com do developer.chrome.com/blog, ponieważ są to strony z tej samej domeny.
Nie możesz mieć tego przejścia podczas nawigacji z developer.chrome.com na www.chrome.com, ponieważ są to witryny tego samego pochodzenia i tej samej domeny.
Przejścia między dokumentami są opcjonalne
Aby przejście między dwoma dokumentami było widoczne, obie strony muszą wyrazić na to zgodę. Służy do tego reguła @view-transition w CSS.
W regule @view-transition ustaw deskryptor navigation na auto, aby włączyć przejścia widoku w przypadku nawigacji między dokumentami w tej samej domenie.
@view-transition {
navigation: auto;
}
Ustawiając deskryptor navigation na auto, wyrażasz zgodę na przejścia widoku w przypadku tych typów NavigationType:
traversepushlubreplace, jeśli aktywacja nie została zainicjowana przez użytkownika za pomocą mechanizmów interfejsu przeglądarki.
Nawigacje wykluczone z auto to np. nawigacja za pomocą paska adresu URL lub kliknięcie zakładki, a także wszelkie formy ponownego wczytywania zainicjowanego przez użytkownika lub skrypt.
Jeśli nawigacja trwa zbyt długo – w przypadku Chrome ponad 4 sekundy – przejście w formie obejrzenia jest pomijane z użyciem TimeoutError DOMException.
Wersja demonstracyjna przejść między dokumentami
Obejrzyj poniższe demo, w którym za pomocą przejść widoku utworzono demo nawigatora stosu. Nie ma tu wywołań document.startViewTransition(), przejścia widoku 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 View Transition API, ale są przeznaczone do używania 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: pageswap i pagereveal.
Te 2 zdarzenia są wywoływane w przypadku każdej nawigacji między dokumentami w tej samej domenie, niezależnie od tego, czy ma nastąpić przejście widoku. Jeśli między dwiema stronami ma nastąpić przejście widoku, możesz uzyskać dostęp do obiektu ViewTransition za pomocą właściwości viewTransition w tych zdarzeniach.
- Zdarzenie
pageswapjest wywoływane przed wyrenderowaniem ostatniej klatki strony. Możesz użyć tego interfejsu, aby wprowadzić ostatnie zmiany na stronie wychodzącej tuż przed zrobieniem starych migawek. - Zdarzenie
pagerevealjest wywoływane na stronie po jej zainicjowaniu lub ponownej aktywacji, ale przed pierwszą możliwością renderowania. Dzięki temu możesz dostosować nową stronę, zanim zostaną zrobione nowe zrzuty.
Możesz na przykład użyć tych zdarzeń, aby szybko ustawić lub zmienić niektóre wartości view-transition-name albo przekazać dane z jednego dokumentu do drugiego, zapisując i odczytując dane z sessionStorage, aby dostosować przejście widoku przed jego 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 pageswap i pagereveal to dwa różne obiekty. Różnie też traktują różne obietnice:
pageswap: Po ukryciu dokumentu stary obiektViewTransitionzostanie pominięty. W takiej sytuacjiviewTransition.readyodrzuca iviewTransition.finishedrozwiązuje.pagereveal: obietnicaupdateCallBackzostała już spełniona. Możesz używać obietnicviewTransition.readyiviewTransition.finished.
Informacje o aktywacji nawigacji
W przypadku zdarzeń pageswap i pagereveal możesz też podejmować działania na podstawie adresów URL starych i nowych stron.
Na przykład w MPA Stack Navigator typ animacji zależy od ścieżki nawigacji:
- Podczas przechodzenia ze strony przeglądu na stronę szczegółów nowa treść musi wsuwać się od prawej do lewej.
- Podczas przechodzenia ze strony szczegółów na stronę przeglądu stara treść musi przesuwać się od lewej do prawej.
W tym celu 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 tej samej domeny. Ten obiekt udostępnia użyty typ nawigacji oraz bieżące i końcowe wpisy historii miejsca docelowego znalezione w navigation.entries() interfejsie Navigation API.
Na aktywowanej stronie możesz uzyskać dostęp do tego obiektu za pomocą navigation.activation. W wydarzeniu pageswap możesz uzyskać do niego dostęp, klikając e.activation.
Obejrzyj tę wersję demonstracyjną profili, która wykorzystuje informacje NavigationActivation w zdarzeniach pageswap i pagereveal do ustawiania wartości view-transition-name w elementach, które muszą brać udział w przejściu widoku.
Dzięki temu nie musisz z góry oznaczać każdego elementu na liście symbolem view-transition-name. Zamiast tego dzieje się to w odpowiednim momencie za pomocą JavaScriptu, 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 czyści też po sobie, usuwając wartości view-transition-name po zakończeniu przejścia widoku. Dzięki temu strona jest gotowa na kolejne nawigacje i może też obsługiwać przechodzenie do poprzednich stron.
Aby to ułatwić, użyj tej funkcji narzędziowej, 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
Browser Support
W niektórych przypadkach możesz chcieć odłożyć pierwsze renderowanie strony do momentu, gdy w nowym DOM pojawi się określony element. Pozwoli to uniknąć migotania i zapewni stabilność stanu, do którego animacja ma prowadzić.
W sekcji <head> zdefiniuj co najmniej 1 identyfikator elementu, który musi być obecny, zanim strona zostanie po raz pierwszy wyrenderowana. Użyj w tym celu tego metatagu:
<link rel="expect" blocking="render" href="#section1">
Ten tag meta oznacza, że element powinien być obecny w DOM, a nie że treść powinna być wczytana. W przypadku obrazów wystarczy, że w drzewie DOM pojawi się tag <img> z określonym atrybutem id, aby warunek przyjął wartość „true” (prawda). Obraz może się jeszcze wczytywać.
Zanim w pełni zastosujesz blokowanie renderowania, pamiętaj, że renderowanie przyrostowe jest podstawowym aspektem internetu, więc zachowaj ostrożność, gdy zdecydujesz się na blokowanie renderowania. Wpływ blokowania renderowania należy oceniać w każdym przypadku z osobna. Domyślnie unikaj używania blocking=render, chyba że możesz aktywnie mierzyć i oceniać jego wpływ na użytkowników, mierząc wpływ na podstawowe wskaźniki internetowe.
Wyświetlanie typów przejść w przejściach widoku między dokumentami
Przejścia widoku między dokumentami obsługują też typy przejść widoku, które pozwalają dostosowywać animacje i elementy, które mają być przechwytywane.
Na przykład podczas przechodzenia do następnej lub poprzedniej strony w podziale na strony możesz używać różnych animacji w zależności od tego, czy przechodzisz do strony o wyższym czy niższym numerze w sekwencji.
Aby z wyprzedzeniem ustawić te typy, dodaj je w regule @@view-transition:
@view-transition {
navigation: auto;
types: slide, forwards;
}
Aby ustawić typy na bieżąco, użyj zdarzeń pageswap i pagereveal do manipulowania wartością 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óre mają być używane co najmniej na nowej stronie.
Aby reagować na te typy, użyj selektora pseudoklasy :active-view-transition-type() w taki sam sposób jak w przypadku przejść widoku w tym samym dokumencie.
/* 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 mają zastosowanie tylko do aktywnego przejścia widoku, są one automatycznie usuwane po zakończeniu przejścia widoku. Dlatego typy dobrze współpracują z funkcjami takimi jak BFCache.
Prezentacja
W poniższym demo paginacji zawartość strony przesuwa się do przodu lub do tyłu w zależności od numeru strony, do której przechodzisz.
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ę
Opinie deweloperów są zawsze mile widziane. Aby się nimi podzielić, zgłoś problem w grupie roboczej CSS na GitHubie, podając sugestie i pytania. Dodaj do problemu prefiks [css-view-transitions].
Jeśli napotkasz błąd, zgłoś go w Chromium.