Het probleem
Websites bieden vaak meerdere weergaven van een object, bijvoorbeeld een lijst met producten en daarna een detailpagina voor elk product. Navigeren tussen de 2 weergaven voelt vaak niet verbonden. Als een gebruiker op een productthumbnail klikt om de details te bekijken, verdwijnt de thumbnail en verschijnt er onmiddellijk een nieuwe, grotere afbeelding ergens anders op het scherm. Door dit gebrek aan continuïteit is het voor gebruikers moeilijker om de relaties tussen elementen te volgen.
De oplossing
Met de View Transitions API kun je elementparen specificeren die in verschillende statussen bestaan vóór en na een overgang. Als je een overgang activeert met document.startViewTransition() in een navigatie op één pagina (SPA), herkent de browser deze gedeelde elementen aan hun gedeelde unieke view-transition-name. Daarna wordt het verschil in positie, grootte en stijl automatisch berekend en worden de elementen soepel geanimeerd van de oude naar de nieuwe status. Deze overgang vindt plaats in de bovenste laag, zelfs boven elementen met hoge z-index-waarden.
Implementatiehandleiding
Stap 1: Verpak statuswijzigingen in startViewTransition
Voor apps met één pagina (SPA's) of eenvoudige statuswijzigingen verpakt u de logica die de DOM updatet in document.startViewTransition. De browser maakt een momentopname van de huidige status, voert de update uit en maakt daarna een momentopname van de nieuwe status.
function navigate(view) {
// MANDATORY: Wrap the update in startViewTransition
document.startViewTransition(() => updateDOM(view));
}
Stap 2: Wijs namen voor gedeelde overgangen toe
Gebruik de CSS-property view-transition-name om de browser te vertellen welke elementen moeten worden gemorpht. De naam kan van alles zijn (behalve none). VERPLICHT: Er mag niet meer dan 1 element voor en na een bepaalde view-transition-name staan. Als er 2 of meer elementen met een bepaalde view-transition-name zijn, wordt het DOM onmiddellijk geüpdatet naar de nieuwe status, zonder overgang.
U kunt meerdere view-transition-name's gebruiken om meerdere paren elementen te morphen. U wilt bijvoorbeeld zowel de productafbeelding als de titel overzetten met afzonderlijke overgangen.
Omdat er meerdere items in de lijstweergave staan, kun je ze niet allemaal dezelfde view-transition-name geven. Dit kan op 2 manieren worden opgelost in een SPA.
Dynamische detailpagina: Wijs aan elk item op de lijstpagina een unieke
view-transition-nametoe en pas die naam dynamisch toe op het overeenkomende element op de detailpagina als het lijstitem is geselecteerd, zoals hier getoond./* 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; }Dynamisch lijstitem: Wijs het element op de detailpagina een
view-transition-nametoe en pas die naam toe op het item op de lijstpagina wanneer het wordt geselecteerd. Verwijder deview-transition-nameuit het item op de lijstpagina als je terugkeert naar de lijstpagina.
Het element #hero op de detailpagina en het geselecteerde element .thumbnail op de lijstpagina delen een view-transition-name.
#hero{
view-transition-name: hero;
}
.thumbnail.selected {
view-transition-name: hero;
}
Als er op een thumbnail wordt geklikt, moeten we de lijstweergave voorbereiden door de view-transition-name toe te wijzen met de klasseselector .selected en wijzigingen aan te brengen in de DOM voordat we de overgang starten.
Daarna kun je document.startViewTransition() bellen en de wijzigingen toepassen om de pagina over te zetten van de detailweergave naar de lijstweergave.
Nadat je terug bent genavigeerd naar de lijstweergave, moet je de weergaveovergangsklassen opschonen om te voorkomen dat de volgende navigatie een foutmelding geeft. Je kunt deze opschoning uitvoeren nadat de promise van de overgang finished is opgelost.
// 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");
},
);
});
}
Welke methode u kiest, hangt af van de use case. Het dynamische lijstitem vereist minder herhaalde CSS, maar meer handmatige JavaScript-opschoning.
Stap 3: Los problemen met de beeldverhouding op
De browser voert standaard een crossfade uit tussen de oude en nieuwe momentopnamen binnen een groep die wordt uitgerekt zodat beide passen. Als je tekst overzet, stel je de breedte van het tekstelement in op fit-content in zowel de oude als de nieuwe weergave, zodat de beeldverhouding van het overgezette element stabiel is.
#list-page .title {
width: fit-content;
}
#detail-page #title {
width: fit-content;
}
Als je elementen overzet die de beeldverhouding wijzigen, moet je misschien de hoogte van de oude en nieuwe pseudoelementen instellen op 100% van het pseudoelement ::view-transition-pair().
::view-transition-old(hero),
::view-transition-new(hero){
height: 100%;
}
De pseudo-elementen zijn momentopnamen van de live elementen, dus je kunt ook object-fit- en object-position-declaraties gebruiken voor meer controle over het overgangseffect.
Praktische tips
- Geef NIET te veel overgangen op. Gebruik gedeelde elementen alleen voor primaire content die de gebruiker actief volgt (bijvoorbeeld hero-afbeeldingen, koppen).
- Verwijder tijdelijke
view-transition-name-waarden WEL nadat de overstap is afgerond om bijwerkingen bij toekomstige overstappen te voorkomen. - Maak GEEN overgangselementen met actieve animaties. Weergaveovergangen werken met momentopnamen, dus animaties lijken te worden onderbroken tijdens de weergaveovergang.
- DO respect user preferences for reduced motion using the
prefers-reduced-motionmedia query. - VERPLICHTE toegankelijkheidsroutering: Weergaveovergangen veranderen paginalay-outs dynamisch, maar beheren de programmatische focus niet. Als de focus blijft op een element dat tijdens de overgang wordt verborgen of verwijderd, wordt de focus verlaten, waardoor gebruikers van toetsenborden en ondersteunende technologie geen context hebben. Verplaats de focus programmatisch naar een geüpdatete paginakop of weergavecontainer (met
tabindex="-1") onmiddellijk nadat de DOM is geüpdatet of wanneer de promise van de weergaveovergangfinishedis opgelost.
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}
Reservestrategieën
De View Transitions API is ontworpen voor progressive enhancement. Browsers die dit niet ondersteunen, voeren de DOM-update onmiddellijk uit zonder animatie.
function navigate(){
if (!document.startViewTransition) {
// Fallback: Just update the DOM
updateDOM();
} else {
document.startViewTransition(() => updateDOM());
}
}