Problem
Witryny często udostępniają wiele widoków obiektu, na przykład listę produktów, a następnie stronę z informacjami o każdym produkcie. Przechodzenie między tymi 2 widokami często wydaje się niespójne. Gdy użytkownik kliknie miniaturę produktu, aby wyświetlić jego szczegóły, miniatura znika, a w innym miejscu ekranu natychmiast pojawia się nowy, większy obraz. Brak ciągłości utrudnia użytkownikom śledzenie relacji między elementami.
Rozwiązanie
Interfejs View Transitions API umożliwia określanie par elementów, które istnieją w różnych stanach przed przejściem i po nim. Podczas wywoływania przejścia za pomocą document.startViewTransition() w aplikacji jednostronicowej (SPA) przeglądarka identyfikuje te elementy współdzielone na podstawie ich unikalnego view-transition-name. Następnie automatycznie oblicza różnicę w ich pozycji, rozmiarze i stylu oraz płynnie animuje je od starego do nowego stanu. To przejście występuje w górnej warstwie, nawet nad elementami o wysokich wartościach z-index.
Przewodnik po implementacji
Krok 1. Umieść zmiany stanu w elemencie startViewTransition
W przypadku aplikacji jednostronicowych (SPA) lub prostych zmian stanu opakuj logikę, która aktualizuje DOM, w document.startViewTransition. Przeglądarka przechwytuje zrzut bieżącego stanu, przeprowadza aktualizację, a następnie przechwytuje nowy stan.
function navigate(view) {
// MANDATORY: Wrap the update in startViewTransition
document.startViewTransition(() => updateDOM(view));
}
Krok 2. Przypisz nazwy wspólnych przejść
Użyj właściwości CSS view-transition-name, aby poinformować przeglądarkę, które elementy powinny zostać przekształcone. Nazwa może być dowolna (z wyjątkiem none). WYMAGANE: przed i po nie może być więcej niż 1 element z danym view-transition-name. Jeśli istnieją co najmniej 2 elementy z danym view-transition-name, model DOM zostanie natychmiast zaktualizowany do nowego stanu bez przejścia.
Możesz użyć wielu elementów view-transition-name, aby przekształcić wiele par elementów. Możesz na przykład zastosować osobne przejścia do zdjęcia produktu i tytułu.
W widoku listy jest wiele elementów, więc nie możesz im wszystkim przypisać tej samej wartości view-transition-name. W przypadku aplikacji jednostronicowej można to rozwiązać na 2 sposoby.
Dynamiczna strona z informacjami: przypisz każdemu elementowi na stronie z listą unikalny atrybut
view-transition-name, a następnie dynamicznie zastosuj tę nazwę do pasującego elementu na stronie z informacjami, gdy element listy zostanie wybrany, jak pokazano tutaj./* In the list view, give each */ #product-1 { view-transition-name: p1 } #product-2 { view-transition-name: p2 } #product-3 { view-transition-name: p3 }function updateDOM(clickedTransitionName){ const hero = document.getElementById("hero"); hero.style.viewTransitionName = clickedTransitionName; }Element listy dynamicznej: przypisz elementowi na stronie z informacjami
view-transition-namei zastosuj tę nazwę do elementu na stronie z listą, gdy zostanie on wybrany. Po powrocie na stronę z listą usuń elementview-transition-namez pozycji na tej stronie.
Element #hero na stronie z informacjami i wybrany element .thumbnail na stronie z listą mają wspólny element view-transition-name.
#hero{
view-transition-name: hero;
}
.thumbnail.selected {
view-transition-name: hero;
}
Po kliknięciu miniatury musimy przygotować widok listy, przypisując view-transition-name za pomocą selektora klasy .selected i wprowadzając zmiany w DOM przed rozpoczęciem przejścia.
Następnie możesz wywołać document.startViewTransition() i zastosować zmiany, aby przejść ze strony szczegółów do widoku listy.
Po powrocie do widoku listy musisz wyczyścić klasy przejścia widoku, aby zapobiec błędom podczas następnej nawigacji. Możesz wykonać to czyszczenie po rozwiązaniu obietnicy finished przejścia.
// Function called when a thumbnail is clicked
function goFromListToDetail(e){
e.currentTarget.classList.add("selected");
const hero = document.getElementById("hero");
const bgColor = getComputedStyle(e.currentTarget).backgroundColor;
hero.style.background = bgColor;
// Trigger the transition, checking for support
if (!document.startViewTransition) {
document.body.classList.add("detail");
// MANDATORY Accessibility Routing: Route focus to the newly revealed heading to announce context and preserve logical tab flow
document.getElementById("detail-heading")?.focus();
return; // MANDATORY: End function execution if view transitions are not supported.
}
const transition = document.startViewTransition(() => {
document.body.classList.add("detail");
});
// MANDATORY Accessibility Routing: Route focus after the view transition resolves
transition.finished.finally(() => {
document.getElementById("detail-heading")?.focus();
});
}
// Function called when navigating from detail back to list view
function goFromDetailToList() {
if (!document.startViewTransition) {
document.body.classList.remove("detail");
document.getElementById("list-heading")?.focus();
return;
}
const transition = document.startViewTransition(() => {
document.body.classList.remove("detail");
});
// Clean up the list view and route focus
transition.finished.finally(() => {
// Route focus back to list view
document.getElementById("list-heading")?.focus();
// Remove selected classList to remove view-transition-names
document.querySelectorAll(".selected").forEach(
(element) => {
element.classList.remove("selected");
},
);
});
}
Wybór metody zależy od przypadku użycia. Dynamiczny element listy wymaga mniej powtarzającego się kodu CSS, ale więcej ręcznego czyszczenia kodu JavaScript.
Krok 3. Rozwiąż problem z rozciąganiem formatu obrazu
Domyślnie przeglądarka stosuje efekt przenikania między starymi i nowymi zrzutami w grupie, która rozciąga się, aby dopasować się do obu. Jeśli przenosisz tekst, ustaw szerokość elementu tekstowego na fit-content zarówno w starym, jak i nowym widoku, aby format obrazu przenoszonego elementu był stabilny.
#list-page .title {
width: fit-content;
}
#detail-page #title {
width: fit-content;
}
Jeśli przechodzisz między elementami, które zmieniają format obrazu, może być konieczne ustawienie wysokości starych i nowych pseudoelementów na 100% pseudoelementu ::view-transition-pair().
::view-transition-old(hero),
::view-transition-new(hero){
height: 100%;
}
Pseudoelementy to migawki elementów aktywnych, więc możesz też używać deklaracji object-fit i object-position, aby mieć większą kontrolę nad efektem przejścia.
Sprawdzone metody
- NIE określaj zbyt wielu przejść. Używaj elementów udostępnionych tylko w przypadku treści głównych, które użytkownik aktywnie śledzi (np. banerów powitalnych, nagłówków).
- USUŃ tymczasowe wartości
view-transition-namepo zakończeniu przejścia, aby uniknąć skutków ubocznych w przyszłych przejściach. - NIE stosuj przejść do elementów z aktywnymi animacjami. Przejścia widoków działają na zrzutach, więc podczas przejścia widoku wszystkie animacje będą wyglądać na wstrzymane.
- DO respect user preferences for reduced motion using the
prefers-reduced-motionmedia query. - OBOWIĄZKOWE: przekierowywanie ułatwień dostępu: przejścia widoku dynamicznie zmieniają układy stron, ale nie zarządzają fokusem programowym. Jeśli fokus pozostanie na elemencie, który jest ukryty lub usunięty podczas przejścia, zostanie on porzucony, co spowoduje, że użytkownicy klawiatury i technologii wspomagających osoby z niepełnosprawnością nie będą mieli kontekstu. Przenieś fokus programowo na zaktualizowany nagłówek strony lub kontener widoku (za pomocą
tabindex="-1") natychmiast po aktualizacji DOM lub po rozwiązaniu obietnicyfinishedprzejścia widoku.
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}
Strategie zastępcze
Interfejs View Transitions API został zaprojektowany z myślą o progresywnym ulepszaniu. Przeglądarki, które nie obsługują tej funkcji, po prostu natychmiast wykonają aktualizację DOM bez animacji.
function navigate(){
if (!document.startViewTransition) {
// Fallback: Just update the DOM
updateDOM();
} else {
document.startViewTransition(() => updateDOM());
}
}