Przejście widoku pojedynczego dokumentu jest nazywane przejściem widoku tego samego dokumentu. Dzieje się tak zazwyczaj w przypadku aplikacji jednostronicowych (SPA), w których do aktualizacji DOM jest używany JavaScript. Przejścia między widokiem tego samego dokumentu są obsługiwane w Chrome od wersji Chrome 111.
Aby aktywować przejście między widokiem tego samego dokumentu, wywołaj document.startViewTransition
:
function handleClick(e) {
// Fallback for browsers that don't support this API:
if (!document.startViewTransition) {
updateTheDOMSomehow();
return;
}
// With a View Transition:
document.startViewTransition(() => updateTheDOMSomehow());
}
Po wywołaniu przeglądarka automatycznie rejestruje zrzuty wszystkich elementów, które mają zadeklarowaną właściwość CSS view-transition-name
.
Następnie wykonuje przekazane wywołanie zwrotne, które aktualizuje DOM, a następnie wykonuje zrzuty nowego stanu.
Są one następnie układane w drzewo pseudoelementów i animowane przy użyciu animacji CSS. Pary migawek ze starego i nowego stanu płynnie przechodzą ze starej pozycji i w nowym rozmiarze do nowej lokalizacji, a zawartość strony zmienia się. Jeśli chcesz, możesz dostosować animacje za pomocą CSS.
Przejście domyślne: przenikanie
Domyślne przejście widoku jest przenikanie, więc jest dobrym wprowadzeniem do interfejsu API:
function spaNavigate(data) {
// Fallback for browsers that don't support this API:
if (!document.startViewTransition) {
updateTheDOMSomehow(data);
return;
}
// With a transition:
document.startViewTransition(() => updateTheDOMSomehow(data));
}
Gdzie updateTheDOMSomehow
zmienia DOM na nowy stan. Możesz to zrobić w dowolny sposób. Możesz na przykład dodawać i usuwać elementy, zmieniać nazwy klas czy zmieniać style.
W ten sposób strony przechodzą przez przenikanie:
Przenikanie nie jest aż tak imponujące. Na szczęście przejścia można dostosować, ale najpierw musisz zrozumieć, jak działa ten podstawowy przenikanie.
Jak to działa
Zaktualizujmy poprzedni przykładowy kod.
document.startViewTransition(() => updateTheDOMSomehow(data));
Po wywołaniu .startViewTransition()
interfejs API przechwytuje bieżący stan strony. Obejmuje to też zrobienie zrzutu dysku.
Po zakończeniu wywołanie zwrotne przekazane do .startViewTransition()
jest wywoływane. To tam zmienia się DOM. Następnie interfejs API przechwytuje nowy stan strony.
Po przechwyceniu nowego stanu interfejs API tworzy pseudoelementowe drzewo w następujący sposób:
::view-transition
└─ ::view-transition-group(root)
└─ ::view-transition-image-pair(root)
├─ ::view-transition-old(root)
└─ ::view-transition-new(root)
Element ::view-transition
znajduje się w nakładce nad całą pozostałą treścią strony. Jest to przydatne, jeśli chcesz ustawić kolor tła przejścia.
::view-transition-old(root)
to zrzut ekranu starego widoku, a ::view-transition-new(root)
to opublikowana reprezentacja nowego widoku. Oba są renderowane jako „zastępowana zawartość” CSS (np. <img>
).
Stary widok jest animowany od opacity: 1
do opacity: 0
, a w nowym jest wyświetlany od opacity: 0
do opacity: 1
, co powoduje przenikanie.
Cała animacja jest wykonywana za pomocą animacji CSS, więc można je dostosować za pomocą CSS.
Dostosowywanie przejścia
Za pomocą CSS można kierować wszystkie pseudoelementy przejścia między widokami, a animacje są definiowane przy użyciu tego języka, można je modyfikować za pomocą istniejących właściwości animacji CSS. Na przykład:
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 5s;
}
Po tej jednej zmianie zanikanie jest teraz bardzo wolne:
OK, to nadal nie jest imponujące. Zamiast tego ten kod implementuje przejście wspólnej osi interfejsu Material Design:
@keyframes fade-in {
from { opacity: 0; }
}
@keyframes fade-out {
to { opacity: 0; }
}
@keyframes slide-from-right {
from { transform: translateX(30px); }
}
@keyframes slide-to-left {
to { transform: translateX(-30px); }
}
::view-transition-old(root) {
animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
}
::view-transition-new(root) {
animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}
A oto wynik:
Przenoszenie wielu elementów
W poprzedniej wersji demonstracyjnej przejście na wspólną oś uwzględnia całą stronę. Sprawdza się to w przypadku większości stron, ale nie wygląda to dobrze w przypadku nagłówka, ponieważ wysuwa się, aby z powrotem wsunąć się z powrotem.
Aby tego uniknąć, możesz wyodrębnić nagłówek z reszty strony i wyświetlać go oddzielnie. W tym celu przypisuje do elementu atrybut view-transition-name
.
.main-header {
view-transition-name: main-header;
}
Wartość view-transition-name
może być dowolna (z wyjątkiem none
, co oznacza brak nazwy przejścia). Służy do jednoznacznego identyfikowania elementu na każdym etapie przejścia.
Efekt:
Teraz nagłówek pozostaje na swoim miejscu i przenika.
Ta deklaracja CSS spowodowała zmianę pseudoelementu w drzewie:
::view-transition
├─ ::view-transition-group(root)
│ └─ ::view-transition-image-pair(root)
│ ├─ ::view-transition-old(root)
│ └─ ::view-transition-new(root)
└─ ::view-transition-group(main-header)
└─ ::view-transition-image-pair(main-header)
├─ ::view-transition-old(main-header)
└─ ::view-transition-new(main-header)
Dostępne są teraz 2 grupy przenoszenia. Jedna w nagłówku, a druga w odniesieniu do pozostałych. Można na nie kierować reklamy niezależnie za pomocą CSS i stosować różne przejścia. Choć w tym przypadku main-header
pozostał z domyślnym przejściem, czyli przenikaniem.
OK, domyślne przejście to nie tylko przejście – ::view-transition-group
również przechodzi:
- Pozycjonowanie i przekształcanie (za pomocą elementu
transform
) - Szerokość
- Wysokość
Do tej pory nie miało to znaczenia, ponieważ nagłówek ma ten sam rozmiar i znajduje się po obu stronach zmiany DOM. Możesz też wyodrębnić tekst z nagłówka:
.main-header-text {
view-transition-name: main-header-text;
width: fit-content;
}
fit-content
jest używany, dzięki czemu element ma rozmiar tekstu, zamiast rozciągać się do pozostałej szerokości. W przeciwnym razie strzałka wstecz zmniejsza rozmiar elementu tekstowego nagłówka, a nie taki sam rozmiar na obu stronach.
Mamy więc do zagrania trzy części:
::view-transition
├─ ::view-transition-group(root)
│ └─ …
├─ ::view-transition-group(main-header)
│ └─ …
└─ ::view-transition-group(main-header-text)
└─ …
Wróćmy do ustawień domyślnych:
Teraz tekst nagłówka przesunie się w bok, aby zrobić miejsce na przycisk Wstecz.
Dzięki view-transition-class
możesz animować wiele pseudoelementów w taki sam sposób
Obsługa przeglądarek
- 125
- 125
- x
- x
Załóżmy, że masz przejście w widoku z kilkoma kartami i tytułem na stronie. Aby animować wszystkie karty oprócz tytułu, musisz utworzyć selektor, który będzie kierowany na każdą kartę.
h1 {
view-transition-name: title;
}
::view-transition-group(title) {
animation-timing-function: ease-in-out;
}
#card1 { view-transition-name: card1; }
#card2 { view-transition-name: card2; }
#card3 { view-transition-name: card3; }
#card4 { view-transition-name: card4; }
…
#card20 { view-transition-name: card20; }
::view-transition-group(card1),
::view-transition-group(card2),
::view-transition-group(card3),
::view-transition-group(card4),
…
::view-transition-group(card20) {
animation-timing-function: var(--bounce);
}
Masz 20 elementów? To 20 selektorów, które musisz napisać. Chcesz dodać nowy element? Potem musisz też rozwinąć selektor, który stosuje style animacji. Niezbyt skalowalny.
Elementu view-transition-class
można użyć w pseudoelementach przejść widoku, aby zastosować tę samą regułę stylu.
#card1 { view-transition-name: card1; }
#card2 { view-transition-name: card2; }
#card3 { view-transition-name: card3; }
#card4 { view-transition-name: card4; }
#card5 { view-transition-name: card5; }
…
#card20 { view-transition-name: card20; }
#cards-wrapper > div {
view-transition-class: card;
}
html::view-transition-group(.card) {
animation-timing-function: var(--bounce);
}
Poniższy przykład kart wykorzystuje poprzedni fragment kodu CSS. Wszystkie karty (w tym nowo dodane) mają taki sam czas wyświetlania z jednym selektorem: html::view-transition-group(.card)
.
Debugowanie przejść
Przejścia widoku są oparte na animacjach CSS, dlatego panel Animacje w Narzędziach deweloperskich w Chrome świetnie nadaje się do debugowania przejść.
Korzystając z panelu Animacje, możesz wstrzymać następną animację, a następnie przewijać ją do przodu i do tyłu. W tym czasie pseudoelementy przejścia znajdziesz w panelu Elementy.
Przenoszone elementy nie muszą być tym samym elementem DOM
Do tej pory użyliśmy view-transition-name
do utworzenia oddzielnych elementów przejścia dla nagłówka i tekstu w nagłówku. Zasadniczo są to te same elementy przed zmianą DOM i po niej, ale można tworzyć przejścia, gdy jest inaczej.
Na przykład dla głównego umieszczonego filmu można zastosować view-transition-name
:
.full-embed {
view-transition-name: full-embed;
}
Następnie po kliknięciu miniatury można mu nadać taki sam view-transition-name
, tylko na czas przejścia:
thumbnail.onclick = async () => {
thumbnail.style.viewTransitionName = 'full-embed';
document.startViewTransition(() => {
thumbnail.style.viewTransitionName = '';
updateTheDOMSomehow();
});
};
Wynik:
Miniatura przesuwa się teraz w główne zdjęcie. Chociaż są to koncepcyjnie (i dosłownie) różne elementy, interfejs API przejść traktuje je jako tę samą rzecz, ponieważ mają ten sam element view-transition-name
.
Rzeczywisty kod przejścia jest nieco bardziej skomplikowany niż poprzedni przykład, ponieważ obsługuje również przejście z powrotem do strony miniatury. Zajrzyj do źródła, aby zapoznać się z pełną implementacją.
Niestandardowe przejścia
Spójrz na ten przykład:
Pasek boczny jest częścią przejścia:
.sidebar {
view-transition-name: sidebar;
}
W przeciwieństwie do nagłówka w poprzednim przykładzie pasek boczny nie wyświetla się jednak na wszystkich stronach. Jeśli oba stany mają pasek boczny, pseudoelementy przejścia wyglądają tak:
::view-transition
├─ …other transition groups…
└─ ::view-transition-group(sidebar)
└─ ::view-transition-image-pair(sidebar)
├─ ::view-transition-old(sidebar)
└─ ::view-transition-new(sidebar)
Jeśli jednak pasek boczny znajduje się tylko na nowej stronie, pseudoelementu ::view-transition-old(sidebar)
tam nie będzie. Nie ma tu „starego” obrazu na pasku bocznym, para obrazów będzie zawierać tylko ::view-transition-new(sidebar)
. Jeśli pasek boczny znajduje się tylko na starej stronie, para obrazów będzie zawierać tylko ::view-transition-old(sidebar)
.
W poprzedniej wersji demonstracyjnej pasek boczny zmienia się w różny sposób w zależności od tego, czy wyświetla się w obu tych stanach, czy z niego wychodzi. Wchodzi, przesuwa się od prawej strony i zanika. Znika, przesuwając w prawo i zanika. Pozostaje na swoim miejscu, jeśli występuje w obu stanach.
Aby utworzyć konkretne przejścia wejściowe i wyjściowe, możesz użyć pseudoklasy :only-child
, aby ustawić kierowanie na stare lub nowe pseudoelementy, jeśli są one jedynymi elementami podrzędnymi w parze obrazów:
/* Entry transition */
::view-transition-new(sidebar):only-child {
animation: 300ms cubic-bezier(0, 0, 0.2, 1) both fade-in,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}
/* Exit transition */
::view-transition-old(sidebar):only-child {
animation: 150ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-right;
}
W tym przypadku nie ma konkretnego przejścia, w którym pasek boczny jest dostępny w obu stanach, ponieważ ustawienie domyślne jest idealne.
Asynchroniczne aktualizacje DOM i oczekiwanie na treść
Wywołanie zwrotne przekazane do .startViewTransition()
może zwrócić obietnicę, która umożliwia asynchroniczne aktualizacje DOM i oczekiwanie na przygotowanie ważnych treści.
document.startViewTransition(async () => {
await something;
await updateTheDOMSomehow();
await somethingElse;
});
Przenoszenie nie rozpocznie się, dopóki obietnica nie zostanie zrealizowana. W tym czasie strona jest zablokowana, więc opóźnienia należy ograniczyć do minimum. W szczególności pobieranie danych z sieci należy wykonać przed wywołaniem funkcji .startViewTransition()
, gdy strona jest wciąż w pełni interaktywna, a nie w ramach wywołania zwrotnego .startViewTransition()
.
Jeśli chcesz poczekać na gotowość obrazów lub czcionek, pamiętaj, by ustawić limit czasu agresywnego:
const wait = ms => new Promise(r => setTimeout(r, ms));
document.startViewTransition(async () => {
updateTheDOMSomehow();
// Pause for up to 100ms for fonts to be ready:
await Promise.race([document.fonts.ready, wait(100)]);
});
W niektórych przypadkach lepiej jednak całkiem tego uniknąć i wykorzystać treści, które już masz.
W pełni wykorzystuj treści, które już masz
W przypadku, gdy miniatura przechodzi do większego obrazu:
Domyślnym przejściem jest przenikanie, co oznacza, że miniatura może zanikać z niewczytanym jeszcze pełnym obrazem.
Możesz na przykład poczekać, aż załaduje się cały obraz, zanim rozpoczniesz przejście. Najlepiej zrobić to przed wywołaniem funkcji .startViewTransition()
, aby strona pozostała interaktywna, a użytkownik mógł zobaczyć wskaźnik postępu ładowania. Jednak w tym przypadku istnieje lepsze rozwiązanie:
::view-transition-old(full-embed),
::view-transition-new(full-embed) {
/* Prevent the default animation,
so both views remain opacity:1 throughout the transition */
animation: none;
/* Use normal blending,
so the new view sits on top and obscures the old view */
mix-blend-mode: normal;
}
Miniatura nie znika, ale znajduje się pod pełnym zdjęciem. Oznacza to, że jeśli nowy widok nie został wczytany, miniatura jest widoczna przez cały okres przejścia. Oznacza to, że przejście może zacząć się od razu i cały obraz może wczytać się w odpowiednim momencie.
To nie zadziałałoby, gdyby nowy widok zawierał przejrzystość. Tym razem wiemy, że tak nie jest, dlatego możemy wprowadzić tę optymalizację.
Zaakceptuj zmiany formatu obrazu
Dla wygody wszystkie dotychczasowe przejścia obejmowały elementy o tym samym współczynniku proporcji, ale nie zawsze tak jest. Co zrobić, jeśli miniatura jest w formacie 1:1, a zdjęcie główne ma proporcje 16:9?
W domyślnym przejściu grupa animuje się od rozmiaru „przed” do rozmiaru „po”. Stary i nowy widok mają 100% szerokości grupy i automatyczną wysokość, co oznacza, że współczynnik proporcji jest utrzymywany niezależnie od wielkości grupy.
To dobre ustawienie domyślne, ale w tym przypadku nie jest to wymagane. Przykłady:
::view-transition-old(full-embed),
::view-transition-new(full-embed) {
/* Prevent the default animation,
so both views remain opacity:1 throughout the transition */
animation: none;
/* Use normal blending,
so the new view sits on top and obscures the old view */
mix-blend-mode: normal;
/* Make the height the same as the group,
meaning the view size might not match its aspect-ratio. */
height: 100%;
/* Clip any overflow of the view */
overflow: clip;
}
/* The old view is the thumbnail */
::view-transition-old(full-embed) {
/* Maintain the aspect ratio of the view,
by shrinking it to fit within the bounds of the element */
object-fit: contain;
}
/* The new view is the full image */
::view-transition-new(full-embed) {
/* Maintain the aspect ratio of the view,
by growing it to cover the bounds of the element */
object-fit: cover;
}
Oznacza to, że przy poszerzaniu szerokości miniatura pozostaje na środku elementu, ale cały obraz zostaje cofnięty. gdy przechodzi się od 1:1 do 16:9.
Więcej informacji znajdziesz w artykule Wyświetlanie przejść: obsługa zmian formatu obrazu.
Używaj zapytań o multimedia do zmiany przejść dla różnych stanów urządzenia
Możesz zastosować różne przejścia na urządzeniach mobilnych i na komputerach. Na przykład w tym przykładzie, który przedstawia pełny slajd z boku na urządzeniu mobilnym, a na komputerze bardziej subtelny:
W tym celu użyj zwykłych zapytań o media:
/* Transitions for mobile */
::view-transition-old(root) {
animation: 300ms ease-out both full-slide-to-left;
}
::view-transition-new(root) {
animation: 300ms ease-out both full-slide-from-right;
}
@media (min-width: 500px) {
/* Overrides for larger displays.
This is the shared axis transition from earlier in the article. */
::view-transition-old(root) {
animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
}
::view-transition-new(root) {
animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}
}
W zależności od pasujących zapytań o multimedia możesz też zmienić elementy, do których przypiszesz view-transition-name
.
Reagowanie na „zmniejszony ruch” preferencja
Użytkownicy mogą za pomocą systemu operacyjnego wskazać, że preferują ograniczenie ruchu. Ustawienie to jest ujawnione w CSS.
Możesz uniemożliwić przeniesienie tych użytkowników:
@media (prefers-reduced-motion) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}
Preferowany jest jednak „zmniejszony ruch”. nie oznacza, że użytkownik chce bez ruchu. Zamiast poprzedniego fragmentu możesz wybrać bardziej subtelną animację, która wciąż odzwierciedla relacje między elementami i przepływ danych.
Obsługa wielu stylów przejść z użyciem typów przejść widoku
Obsługa przeglądarek
- 125
- 125
- x
- x
Czasami przejście z jednego widoku do innego powinno być dopasowane do innych. Gdy na przykład przechodzisz na następną lub poprzednią stronę w sekwencji podziału na strony, możesz przesuwać treść w różnym kierunku w zależności od tego, czy przechodzisz na wyższą, czy na niższą stronę z sekwencji.
Możesz do tego użyć typów przejść w widoku, które pozwalają przypisać co najmniej 1 typ do przejścia w widoku aktywnym. Na przykład podczas przechodzenia na wyższą wersję strony w sekwencji podziału na strony używaj typu forwards
, a podczas przechodzenia na niższą stronę – typu backwards
. Te typy animacji są aktywne tylko przy przechwytywaniu lub wykonywaniu przejścia. Każdy typ można dostosować za pomocą CSS, tak aby uzyskać różne animacje.
Aby używać typów w widoku tego samego dokumentu, przekaż types
do metody startViewTransition
. Aby to umożliwić, document.startViewTransition
akceptuje też obiekt: update
to funkcja wywołania zwrotnego, która aktualizuje DOM, a types
to tablica z typami.
const direction = determineBackwardsOrForwards();
const t = document.startViewTransition({
update: updateTheDOMSomehow,
types: ['slide', direction],
});
Aby zareagować na te typy, użyj selektora :active-view-transition-type()
. Przekaż do selektora element type
, na który chcesz kierować reklamy. Dzięki temu style wielu przejść widoku można oddzielić od siebie, a deklaracje jednego z nich nie będą kolidować z deklaracjami drugiego.
Typy mają zastosowanie tylko podczas przechwytywania lub wykonywania przejścia, więc za pomocą selektora możesz ustawić lub cofnąć ustawienie view-transition-name
w elemencie tylko dla przejścia w widoku tego typu.
/* 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 (using the default root snapshot) */
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;
}
}
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. Typy reklam są określane na podstawie kliknięcia, po którym są przekazywane do parametru document.startViewTransition
.
Aby ustawić kierowanie na dowolne przejście w widoku aktywnym, niezależnie od jego typu, możesz zamiast tego użyć selektora pseudoklasy :active-view-transition
.
html:active-view-transition {
…
}
Obsługa wielu stylów przejść z użyciem nazwy klasy w poziomie głównym przejść widoku
Czasami przejście z jednego określonego typu widoku na inny powinno być odpowiednio dostosowane. lub „wstecz” nawigacja nie powinna się różnić nawigacji.
Przed typami przejścia sposobem obsługi takich przypadków było tymczasowe ustawienie nazwy klasy w katalogu głównym przejścia. W przypadku wywołania metody document.startViewTransition
ten poziom główny przejścia to element <html>
dostępny za pomocą metody document.documentElement
w języku JavaScript:
if (isBackNavigation) {
document.documentElement.classList.add('back-transition');
}
const transition = document.startViewTransition(() =>
updateTheDOMSomehow(data)
);
try {
await transition.finished;
} finally {
document.documentElement.classList.remove('back-transition');
}
Aby usunąć klasy po zakończeniu przejścia, w tym przykładzie użyto elementu transition.finished
, czyli obietnicy wygasającej po osiągnięciu stanu końcowego. Inne właściwości tego obiektu opisano w dokumentacji interfejsu API.
Możesz teraz użyć nazwy klasy w CSS, aby zmienić przejście:
/* 'Forward' transitions */
::view-transition-old(root) {
animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
}
::view-transition-new(root) {
animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in, 300ms
cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}
/* Overrides for 'back' transitions */
.back-transition::view-transition-old(root) {
animation-name: fade-out, slide-to-right;
}
.back-transition::view-transition-new(root) {
animation-name: fade-in, slide-from-left;
}
Podobnie jak w przypadku zapytań o multimedia, obecność tych klas może też służyć do zmiany, które elementy otrzymują view-transition-name
.
Uruchamiaj przejścia bez blokowania innych animacji
Spójrz na tę demonstrację zmiany pozycji przejścia w filmie:
Czy coś jest w nim nie tak? Nie przejmuj się, jeśli tak się nie stało. Tutaj jest spowolnione:
W trakcie przejścia film się zawiesza, a potem odtwarzana jest jego wersja. Dzieje się tak, ponieważ ::view-transition-old(video)
to zrzut ekranu starego widoku, a ::view-transition-new(video)
to aktywny obraz nowego widoku.
Możesz to naprawić, ale najpierw zastanów się, czy warto to robić. Jeśli nie widzisz informacji o problemie gdy przejście było odtwarzane z normalną szybkością, nie chciałbym go zmieniać.
Jeśli naprawdę chcesz rozwiązać ten problem, nie pokazuj ::view-transition-old(video)
. przełącz się prosto do usługi ::view-transition-new(video)
. Możesz to zrobić, zastępując domyślne style i animacje:
::view-transition-old(video) {
/* Don't show the frozen old view */
display: none;
}
::view-transition-new(video) {
/* Don't fade the new view in */
animation: none;
}
To wszystko.
Teraz film będzie odtwarzany przez całe przejście.
Animowanie z użyciem JavaScriptu
Do tej pory wszystkie przejścia były definiowane za pomocą CSS, ale czasami ten sposób nie wystarcza:
Niektórych części tego przejścia nie da się zrealizować za pomocą samego CSS:
- Animacja rozpoczyna się w miejscu kliknięcia.
- Na końcu animacji okrąg ma promień do najdalszego rogu. Mamy jednak nadzieję, że jest to możliwe w przyszłości w usłudze porównywania cen.
Na szczęście możesz tworzyć przejścia za pomocą interfejsu Web Animation API.
let lastClick;
addEventListener('click', event => (lastClick = event));
function spaNavigate(data) {
// Fallback for browsers that don't support this API:
if (!document.startViewTransition) {
updateTheDOMSomehow(data);
return;
}
// Get the click position, or fallback to the middle of the screen
const x = lastClick?.clientX ?? innerWidth / 2;
const y = lastClick?.clientY ?? innerHeight / 2;
// Get the distance to the furthest corner
const endRadius = Math.hypot(
Math.max(x, innerWidth - x),
Math.max(y, innerHeight - y)
);
// With a transition:
const transition = document.startViewTransition(() => {
updateTheDOMSomehow(data);
});
// Wait for the pseudo-elements to be created:
transition.ready.then(() => {
// Animate the root's new view
document.documentElement.animate(
{
clipPath: [
`circle(0 at ${x}px ${y}px)`,
`circle(${endRadius}px at ${x}px ${y}px)`,
],
},
{
duration: 500,
easing: 'ease-in',
// Specify which pseudo-element to animate
pseudoElement: '::view-transition-new(root)',
}
);
});
}
W tym przykładzie wykorzystano obietnicę transition.ready
, która wygasa po utworzeniu pseudoelementów przejścia. Inne właściwości tego obiektu opisano w dokumentacji interfejsu API.
Przejścia jako ulepszenie
Interfejs View Przenoszenie API został zaprojektowany do „zawijania” zmianę DOM i utworzenie dla niej przejścia. Przejście należy jednak traktować jako ulepszenie – aplikacja nie może zawierać błędu w przypadku, gdy zmiana DOM się powiedzie, ale przejście się nie powiedzie. W idealnej sytuacji przeniesienie nie powinno się zakończyć niepowodzeniem, ale jeśli do niego dojdzie, nie powinno to negatywnie wpłynąć na wrażenia użytkownika.
Aby traktować przejścia jako ulepszenie, uważaj, aby nie korzystać z obietnic przejścia w sposób, który mógłby spowodować, że w razie niepowodzenia przejścia aplikacja się zakończy.
async function switchView(data) { // Fallback for browsers that don't support this API: if (!document.startViewTransition) { await updateTheDOM(data); return; } const transition = document.startViewTransition(async () => { await updateTheDOM(data); }); await transition.ready; document.documentElement.animate( { clipPath: [`inset(50%)`, `inset(0)`], }, { duration: 500, easing: 'ease-in', pseudoElement: '::view-transition-new(root)', } ); }
Problem z tym przykładem polega na tym, że jeśli przejście nie może osiągnąć stanu ready
, switchView()
odrzuci żądanie, ale nie oznacza to, że nie udało się przełączyć widoku. DOM mógł zostać zaktualizowany, ale przejście zostało pominięte, bo elementy view-transition-name
były zduplikowane.
Zamiast tego:
async function switchView(data) { // Fallback for browsers that don't support this API: if (!document.startViewTransition) { await updateTheDOM(data); return; } const transition = document.startViewTransition(async () => { await updateTheDOM(data); }); animateFromMiddle(transition); await transition.updateCallbackDone; } async function animateFromMiddle(transition) { try { await transition.ready; document.documentElement.animate( { clipPath: [`inset(50%)`, `inset(0)`], }, { duration: 500, easing: 'ease-in', pseudoElement: '::view-transition-new(root)', } ); } catch (err) { // You might want to log this error, but it shouldn't break the app } }
W tym przykładzie użyto metody transition.updateCallbackDone
do oczekiwania na aktualizację DOM i odrzucenia go w przypadku niepowodzenia. Moduł switchView
nie odrzuca już konwersji w przypadku niepowodzenia, koryguje po zakończeniu aktualizacji DOM i odrzuca w przypadku niepowodzenia.
Jeśli chcesz, by reguła switchView
uwzględniała tylko zmiany w nowym widoku (np. wszystkie animowane przejścia zostały ukończone lub pominięte do końca), zastąp transition.updateCallbackDone
elementem transition.finished
.
To nie jest kod polyfill, ale...
Korzystanie z kodu polyfill nie jest łatwe. Jednak funkcja pomocnicza znacznie ułatwia sprawę w przeglądarkach, które nie obsługują przejść między widokami:
function transitionHelper({
skipTransition = false,
types = [],
update,
}) {
const unsupported = (error) => {
const updateCallbackDone = Promise.resolve(update()).then(() => {});
return {
ready: Promise.reject(Error(error)),
updateCallbackDone,
finished: updateCallbackDone,
skipTransition: () => {},
types,
};
}
if (skipTransition || !document.startViewTransition) {
return unsupported('View Transitions are not supported in this browser');
}
try {
const transition = document.startViewTransition({
update,
types,
});
return transition;
} catch (e) {
return unsupported('View Transitions with types are not supported in this browser');
}
}
Można go użyć w ten sposób:
function spaNavigate(data) {
const types = isBackNavigation ? ['back-transition'] : [];
const transition = transitionHelper({
update() {
updateTheDOMSomehow(data);
},
types,
});
// …
}
W przeglądarkach, które nie obsługują przejść z widokiem, polecenie updateDOM
będzie nadal wywoływane, ale przejście nie będzie animowane.
Możesz też podać classNames
do dodania do elementu <html>
podczas przejścia, co ułatwi jego zmianę w zależności od rodzaju nawigacji.
Jeśli nie chcesz korzystać z animacji, możesz też przekazać true
do skipTransition
, nawet w przeglądarkach, które obsługują przejścia między widokami. Jest to przydatne, jeśli użytkownicy w Twojej witrynie wolą wyłączyć przenoszenie.
Praca z platformami
Jeśli pracujesz z biblioteką lub platformą, która wyodrębnia zmiany DOM, trudno jest stwierdzić, kiedy zmiana DOM zostaje zakończona. Oto kilka przykładów, które korzystają z powyższej pomocy na różnych platformach.
- React – klucz to
flushSync
, który stosuje synchronicznie zbiór zmian stanu. Tak, pojawia się duże ostrzeżenie dotyczące używania tego interfejsu API, ale Dan Abramov zapewnia mi, że w tym przypadku jest on odpowiedni. Jak zwykle w przypadku kodu React i kodu asynchronicznego podczas korzystania z różnych obietnic zwracanych przez funkcjęstartViewTransition
sprawdź, czy kod działa w prawidłowym stanie. - Vue.js – klucz to
nextTick
, który jest realizowany po zaktualizowaniu DOM. - Svelte – bardzo podobny do Vue, z tą różnicą, że metodą oczekiwania na następną zmianę jest
tick
. - Lit – kluczem jest tutaj obietnica
this.updateComplete
w komponentach, która zostaje zrealizowana po zaktualizowaniu DOM. - Angular – klucz to
applicationRef.tick
, który usuwa oczekujące zmiany DOM. Od Angular w wersji 17 możesz używać usługiwithViewTransitions
, która zawiera@angular/router
.
Dokumentacja API
const viewTransition = document.startViewTransition(update)
Rozpocznij nowe
ViewTransition
.update
to funkcja, która jest wywoływana po przechwyceniu bieżącego stanu dokumentu.Następnie, gdy spełni się obietnica zwrócona przez funkcję
updateCallback
, przejście rozpocznie się w następnej klatce. Jeśli obietnica zwrócona przez użytkownikaupdateCallback
zostanie odrzucona, przeniesienie zostanie anulowane.const viewTransition = document.startViewTransition({ update, types })
Rozpocznij nowy element
ViewTransition
o określonych typachFunkcja
update
jest wywoływana po przechwyceniu bieżącego stanu dokumentu.types
określa aktywne typy przejścia podczas ich przechwytywania. Początkowo jest on pusty. Więcej informacji znajdziesz w sekcjiviewTransition.types
.
Członkowie instancji ViewTransition
:
viewTransition.updateCallbackDone
Obietnica spełniająca się, gdy obietnica zwrócona przez firmę
updateCallback
zostaje zrealizowana, lub odrzucona, gdy została odrzucona.Interfejs View Migrate API opakowuje zmianę DOM i tworzy przejście. Czasem jednak nie zależy Ci na sukcesie lub niepowodzeniu animacji przejścia, być może chcesz po prostu wiedzieć, czy i kiedy nastąpi zmiana DOM.
updateCallbackDone
jest właśnie w tym przypadku użycia.viewTransition.ready
Obietnica, która zostaje zrealizowana po utworzeniu pseudoelementów przejścia i za chwilę rozpocznie się animacja.
Jeśli nie można rozpocząć przejścia, zostanie odrzucone. Może to być spowodowane błędną konfiguracją, na przykład zduplikowaniem elementów
view-transition-name
lub gdyupdateCallback
zwraca odrzuconą obietnicę.Jest to przydatne do animowania pseudoelementów przejścia za pomocą JavaScriptu.
viewTransition.finished
Obietnica spełniająca się, gdy stan końcowy jest w pełni widoczny i interaktywny dla użytkownika.
Odrzuca tylko wtedy, gdy
updateCallback
zwraca odrzuconą obietnicę, ponieważ oznacza to, że stan końcowy nie został utworzony.W przeciwnym razie, jeśli przejście się nie rozpocznie lub zostanie w jego trakcie pominięte, stan końcowy nadal będzie miał miejsce, więc
finished
realizuje zamierzony cel.viewTransition.types
Obiekt podobny do
Set
, który zawiera typy przejść z widoku aktywnego. Aby manipulować wpisami, użyj metod instancjiclear()
,add()
idelete()
.Aby zareagować na określony typ w CSS, użyj selektora pseudoklasy
:active-view-transition-type(type)
na poziomie głównym przejścia.Typy są automatycznie czyszczone po zakończeniu przejścia między widokami.
viewTransition.skipTransition()
Pomiń fragment przejścia z animacją.
Nie spowoduje to pominięcia wywołania
updateCallback
, ponieważ zmiana DOM jest odrębna od przejścia.
Domyślny styl i odniesienie do przejścia
::view-transition
- Główny pseudoelement, który wypełnia widoczny obszar i zawiera wszystkie elementy
::view-transition-group
. ::view-transition-group
Bezwzględna pozycja.
Przenosi o
width
iheight
między elementem „przed” i „po” stanów.Przenosi o
transform
między elementem „przed” i „po” poczwórny obszar widocznego obszaru.::view-transition-image-pair
Pozycjonowane, aby wypełnić grupę.
Zawiera element
isolation: isolate
, aby ograniczyć wpływ elementumix-blend-mode
na stare i nowe widoki.::view-transition-new
i::view-transition-old
Musi znajdować się w lewym górnym rogu opakowania.
Wypełnia 100% szerokości grupy, ale ma ustawioną automatyczną wysokość, więc zachowa współczynnik proporcji zamiast zapełniać grupę.
Ma wartość
mix-blend-mode: plus-lighter
, która umożliwia przenikanie.Stary widok zostanie zmieniony z
opacity: 1
naopacity: 0
. Nowy widok danych zmieni się zopacity: 0
naopacity: 1
.
Prześlij opinię
Opinie deweloperów są dla nas bardzo ważne. Aby to zrobić, zgłoś problem w grupie roboczej ds. usług porównywania cen 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.