Gepubliceerd: 17 augustus 2021, Laatst bijgewerkt: 25 september 2024
Wanneer een weergaveovergang op één document wordt uitgevoerd, spreken we van een weergaveovergang binnen hetzelfde document . Dit is doorgaans het geval bij single-page applicaties (SPA's) waar JavaScript wordt gebruikt om de DOM bij te werken. Weergaveovergangen binnen hetzelfde document worden in Chrome ondersteund vanaf versie 111.
Om een overgang tussen weergaven van hetzelfde document te activeren, roept u document.startViewTransition aan:
function handleClick(e) {
// Fallback for browsers that don't support this API:
if (!document.startViewTransition) {
updateTheDOMSomehow();
return;
}
// With a View Transition:
document.startViewTransition(() => updateTheDOMSomehow());
}
Wanneer deze functie wordt aangeroepen, maakt de browser automatisch momentopnamen van alle elementen waarop de CSS-eigenschap ` view-transition-name is gedeclareerd.
Vervolgens voert het de meegegeven callback uit die de DOM bijwerkt, waarna het momentopnamen maakt van de nieuwe toestand.
Deze momentopnamen worden vervolgens gerangschikt in een boomstructuur van pseudo-elementen en geanimeerd met behulp van CSS-animaties. Paren van momentopnamen uit de oude en nieuwe staat gaan vloeiend over van hun oude positie en grootte naar hun nieuwe locatie, terwijl de inhoud in elkaar overvloeit. Desgewenst kunt u de animaties aanpassen met CSS.
De standaardovergang: Crossfade
De standaard overgang tussen schermen is een crossfade, wat een prettige introductie tot de API oplevert:
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));
}
Bij updateTheDOMSomehow wordt de DOM op een of andere manier naar een nieuwe staat gewijzigd. Dit kan op verschillende manieren. Je kunt bijvoorbeeld elementen toevoegen of verwijderen, klassenamen wijzigen of stijlen aanpassen.
En plotseling vloeien de pagina's in elkaar over:
Oké, een crossfade is niet zo indrukwekkend. Gelukkig kunnen overgangen worden aangepast, maar eerst moet je begrijpen hoe deze basis crossfade werkt.
Hoe deze overgangen werken
Laten we het vorige codevoorbeeld bijwerken.
document.startViewTransition(() => updateTheDOMSomehow(data));
Wanneer .startViewTransition() wordt aangeroepen, legt de API de huidige status van de pagina vast. Dit omvat ook het maken van een momentopname.
Zodra dit is voltooid, wordt de callback die is doorgegeven aan .startViewTransition() aangeroepen. Daar wordt de DOM gewijzigd. Vervolgens legt de API de nieuwe status van de pagina vast.
Zodra de nieuwe status is vastgelegd, construeert de API een pseudo-elementenboom zoals deze:
::view-transition
└─ ::view-transition-group(root)
└─ ::view-transition-image-pair(root)
├─ ::view-transition-old(root)
└─ ::view-transition-new(root)
De ::view-transition wordt als een overlay over de rest van de pagina geplaatst. Dit is handig als je een achtergrondkleur voor de overgang wilt instellen.
::view-transition-old(root) is een screenshot van de oude weergave, en ::view-transition-new(root) is een live weergave van de nieuwe weergave. Beide worden weergegeven als CSS 'vervangende inhoud' (zoals een <img> ).
De oude weergave verandert geleidelijk van opacity: 1 naar opacity: 0 , terwijl de nieuwe weergave geleidelijk verandert van opacity: 0 naar opacity: 1 , waardoor een crossfade ontstaat.
Alle animaties worden uitgevoerd met CSS-animaties, waardoor ze met CSS kunnen worden aangepast.
De overgang aanpassen
Alle overgangs-pseudo-elementen kunnen met CSS worden aangestuurd, en aangezien de animaties met CSS zijn gedefinieerd, kunt u ze aanpassen met behulp van bestaande CSS-animatie-eigenschappen. Bijvoorbeeld:
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 5s;
}
Door die ene verandering verloopt het vervagen nu echt heel langzaam:
Oké, dat is nog steeds niet indrukwekkend. In plaats daarvan implementeert de volgende code de gedeelde-asovergang van 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;
}
En dit is het resultaat:
Meerdere elementen overzetten
In de vorige demo was de hele pagina betrokken bij de overgang via de gedeelde as. Dat werkt voor het grootste deel van de pagina, maar het lijkt niet helemaal geschikt voor de koptekst, omdat die naar buiten schuift om vervolgens weer naar binnen te schuiven.
Om dit te voorkomen, kunt u de header loskoppelen van de rest van de pagina, zodat deze afzonderlijk kan worden geanimeerd. Dit doet u door een view-transition-name toe te wijzen aan het element.
.main-header {
view-transition-name: main-header;
}
De waarde van view-transition-name kan willekeurig zijn (behalve none , wat betekent dat er geen overgangsnaam is). Deze waarde wordt gebruikt om het element uniek te identificeren tijdens de overgang.
En het resultaat daarvan:
Nu blijft de koptekst op zijn plaats en vervaagt deze.
Die CSS-declaratie zorgde ervoor dat de pseudo-elementstructuur veranderde:
::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)
Er zijn nu twee overgangsgroepen. Eén voor de header en één voor de rest. Deze kunnen onafhankelijk van elkaar met CSS worden aangestuurd en van verschillende overgangen worden voorzien. In dit geval is voor de main-header de standaardovergang, een crossfade, behouden.
Oké, de standaardovergang is dus niet alleen een crossfade, de ::view-transition-group voert ook overgangen uit:
- Positie en transformatie (met behulp van een
transform) - Breedte
- Hoogte
Dat was tot nu toe geen probleem, omdat de header aan beide zijden van de DOM dezelfde grootte en positie behoudt. Maar je kunt de tekst in de header ook extraheren:
.main-header-text {
view-transition-name: main-header-text;
width: fit-content;
}
fit-content wordt gebruikt zodat het element de grootte van de tekst heeft, in plaats van zich uit te rekken tot de resterende breedte. Zonder dit zou de terugpijl de grootte van het koptekstelement verkleinen, in plaats van dat het op beide pagina's dezelfde grootte behoudt.
We hebben nu dus drie onderdelen om mee te spelen:
::view-transition
├─ ::view-transition-group(root)
│ └─ …
├─ ::view-transition-group(main-header)
│ └─ …
└─ ::view-transition-group(main-header-text)
└─ …
Maar nogmaals, we gebruiken gewoon de standaardinstellingen:
Nu schuift de koptekst een beetje opzij om ruimte te maken voor de terugknop.
Animeer meerdere pseudo-elementen op dezelfde manier met view-transition-class
Stel, je hebt een overgangsweergave met een aantal kaarten, maar ook een titel op de pagina. Om alle kaarten behalve de titel te animeren, moet je een selector schrijven die elke afzonderlijke kaart selecteert.
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);
}
Heb je 20 elementen? Dan moet je 20 selectors schrijven. Voeg je een nieuw element toe? Dan moet je ook de selector die de animatiestijlen toepast, aanpassen. Niet echt schaalbaar.
De view-transition-class kan in de view transition pseudo-elementen worden gebruikt om dezelfde stijlregel toe te passen.
#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);
}
Het volgende voorbeeld met kaarten maakt gebruik van het vorige CSS-fragment. Alle kaarten – inclusief nieuw toegevoegde kaarten – krijgen dezelfde timing met één selector: html::view-transition-group(.card) .
view-transition-class wordt dezelfde animation-timing-function toegepast op alle kaarten, behalve op de toegevoegde of verwijderde kaarten.Debug-overgangen
Omdat overgangen tussen weergaven gebaseerd zijn op CSS-animaties, is het paneel Animaties in Chrome DevTools uitstekend geschikt voor het debuggen van overgangen.
Via het paneel Animaties kunt u de volgende animatie pauzeren en vervolgens heen en weer door de animatie scrollen. Tijdens dit proces vindt u de overgangs-pseudo-elementen in het paneel Elementen .
De elementen die tijdens een overgang verschijnen, hoeven niet hetzelfde DOM-element te zijn.
Tot nu toe hebben we view-transition-name gebruikt om aparte overgangselementen te creëren voor de header en de tekst in de header. Conceptueel gezien zijn dit dezelfde elementen vóór en na de DOM-wijziging, maar je kunt overgangen creëren waarbij dat niet het geval is.
De hoofdvideo-embed kan bijvoorbeeld een view-transition-name krijgen:
.full-embed {
view-transition-name: full-embed;
}
Als er vervolgens op de miniatuur wordt geklikt, kan deze dezelfde view-transition-name krijgen, maar alleen voor de duur van de overgang:
thumbnail.onclick = async () => {
thumbnail.style.viewTransitionName = 'full-embed';
document.startViewTransition(() => {
thumbnail.style.viewTransitionName = '';
updateTheDOMSomehow();
});
};
En het resultaat:
De miniatuurafbeelding gaat nu over in de hoofdafbeelding. Hoewel het conceptueel (en letterlijk) verschillende elementen zijn, behandelt de overgangs-API ze als hetzelfde, omdat ze dezelfde view-transition-name delen.
De daadwerkelijke code voor deze overgang is iets complexer dan het voorgaande voorbeeld, omdat deze ook de overgang terug naar de miniatuurpagina afhandelt. Zie de broncode voor de volledige implementatie.
Aangepaste in- en uitgangen
Kijk eens naar dit voorbeeld:
De zijbalk maakt deel uit van de overgang:
.sidebar {
view-transition-name: sidebar;
}
Maar in tegenstelling tot de header in het vorige voorbeeld, verschijnt de zijbalk niet op alle pagina's. Als beide staten de zijbalk hebben, zien de overgangspseudo-elementen er als volgt uit:
::view-transition
├─ …other transition groups…
└─ ::view-transition-group(sidebar)
└─ ::view-transition-image-pair(sidebar)
├─ ::view-transition-old(sidebar)
└─ ::view-transition-new(sidebar)
Als de zijbalk echter alleen op de nieuwe pagina staat, zal het pseudo-element ::view-transition-old(sidebar) er niet zijn. Omdat er geen 'oude' afbeelding voor de zijbalk is, zal het afbeeldingspaar alleen een ::view-transition-new(sidebar) bevatten. Op dezelfde manier zal het afbeeldingspaar, als de zijbalk alleen op de oude pagina staat, alleen een ::view-transition-old(sidebar) bevatten.
In de vorige demo zag je dat de overgang van de zijbalk anders was, afhankelijk van of deze verscheen, verdween of in beide toestanden aanwezig was. Bij het verschijnen schuift de zijbalk van rechts naar binnen en vervaagt hij, bij het verdwijnen schuift hij naar rechts naar buiten en vervaagt hij, en blijft hij op zijn plaats wanneer hij in beide toestanden aanwezig is.
Om specifieke overgangen voor het in- en uitgaan van een afbeelding te creëren, kunt u de pseudo-klasse :only-child gebruiken om de oude of nieuwe pseudo-elementen te selecteren wanneer dit het enige kindelement in het afbeeldingspaar is:
/* 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;
}
In dit geval is er geen specifieke overgang voor het geval de zijbalk in beide staten aanwezig is, aangezien de standaardinstelling perfect is.
Asynchrone DOM-updates en wachten op inhoud
De callback die aan .startViewTransition() wordt doorgegeven, kan een promise retourneren, waardoor asynchrone DOM-updates mogelijk zijn en er gewacht kan worden tot belangrijke content gereed is.
document.startViewTransition(async () => {
await something;
await updateTheDOMSomehow();
await somethingElse;
});
De overgang wordt pas gestart als de belofte is vervuld. Gedurende deze tijd is de pagina bevroren, dus vertragingen moeten tot een minimum beperkt worden. Concreet moeten netwerkverzoeken worden uitgevoerd vóórdat .startViewTransition() wordt aangeroepen, terwijl de pagina nog volledig interactief is, in plaats van ze uit te voeren als onderdeel van de .startViewTransition() -callback.
Als u ervoor kiest te wachten tot afbeeldingen of lettertypen gereed zijn, zorg er dan voor dat u een ruime time-out instelt:
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)]);
});
In sommige gevallen is het echter beter om de vertraging helemaal te vermijden en de inhoud te gebruiken die je al hebt.
Haal het maximale uit de content die je al hebt.
In het geval dat de miniatuur overgaat in een grotere afbeelding:
De standaardovergang is een crossfade, wat betekent dat de miniatuurafbeelding kan overgaan in een nog niet geladen volledige afbeelding.
Een manier om dit aan te pakken is te wachten tot de volledige afbeelding is geladen voordat de overgang begint. Idealiter zou dit gebeuren vóór het aanroepen van .startViewTransition() , zodat de pagina interactief blijft en een laadindicator kan worden weergegeven om de gebruiker te laten weten dat er wordt geladen. Maar in dit geval is er een betere manier:
::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;
}
De miniatuur verdwijnt nu niet meer, maar blijft onder de volledige afbeelding staan. Dit betekent dat als de nieuwe weergave nog niet geladen is, de miniatuur tijdens de overgang zichtbaar blijft. Hierdoor kan de overgang direct starten en kan de volledige afbeelding op zijn eigen tempo laden.
Dit zou niet werken als de nieuwe weergave transparantie zou bevatten, maar in dit geval weten we dat dit niet zo is, dus kunnen we deze optimalisatie doorvoeren.
Wijzigingen in de beeldverhouding verwerken
Handig is dat alle overgangen tot nu toe naar elementen met dezelfde beeldverhouding zijn gegaan, maar dat zal niet altijd het geval zijn. Wat als de miniatuur 1:1 is en de hoofdafbeelding 16:9?
Bij de standaardovergang animeert de groep van de oorspronkelijke grootte naar de nieuwe grootte. Zowel de oude als de nieuwe weergave hebben de volledige breedte van de groep en een automatische hoogte, wat betekent dat de beeldverhouding behouden blijft, ongeacht de grootte van de groep.
Dit is een prima standaardinstelling, maar niet wat in dit geval gewenst is. Dus:
::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;
}
Dit betekent dat de miniatuur in het midden van het element blijft staan terwijl de breedte toeneemt, maar de volledige afbeelding 'niet wordt bijgesneden' wanneer deze overgaat van 1:1 naar 16:9.
Voor meer gedetailleerde informatie kunt u terecht bij Weergaveovergangen: Omgaan met wijzigingen in de beeldverhouding.
Gebruik mediaqueries om overgangen aan te passen aan verschillende apparaatstatussen.
Mogelijk wilt u verschillende overgangen gebruiken voor mobiele apparaten en desktops, zoals in dit voorbeeld waarbij op mobiel een volledige zijwaartse beweging wordt gemaakt, maar op desktop een subtielere beweging:
Dit kan worden bereikt met behulp van reguliere mediaqueries:
/* 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;
}
}
U kunt er ook voor kiezen om te wijzigen aan welke elementen u een view-transition-name toewijst, afhankelijk van overeenkomende mediaqueries.
Reageer op de voorkeur voor 'minder beweging'.
Gebruikers kunnen via hun besturingssysteem aangeven dat ze de voorkeur geven aan minder beweging, en die voorkeur wordt weergegeven in CSS .
U kunt ervoor kiezen om alle overgangen voor deze gebruikers te blokkeren:
@media (prefers-reduced-motion) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}
Een voorkeur voor 'minder beweging' betekent echter niet dat de gebruiker helemaal geen beweging wil. In plaats van het voorgaande fragment zou je een subtielere animatie kunnen kiezen, maar wel een die de relatie tussen elementen en de gegevensstroom weergeeft.
Beheer meerdere weergaveovergangsstijlen met weergaveovergangstypen.
Soms vereist de overgang van de ene weergave naar de andere een specifiek aangepaste overgang. Bijvoorbeeld, wanneer je naar de volgende of vorige pagina in een paginering gaat, wil je de inhoud wellicht in een andere richting verschuiven, afhankelijk van of je naar een hogere of lagere pagina in de reeks gaat.
Hiervoor kunt u overgangstypen gebruiken, waarmee u een of meer typen kunt toewijzen aan een actieve overgang. Bijvoorbeeld: gebruik het type forwards bij het overgaan naar een hogere pagina in een paginering en het type ' backwards bij het overgaan naar een lagere pagina. Deze typen zijn alleen actief tijdens het vastleggen of uitvoeren van een overgang, en elk type kan via CSS worden aangepast om verschillende animaties te gebruiken.
Om typen te gebruiken bij een overgang tussen weergaven binnen hetzelfde document, geef je types door aan de startViewTransition methode. Hiervoor accepteert document.startViewTransition ook een object: update is de callbackfunctie die de DOM bijwerkt, en types is een array met de typen.
const direction = determineBackwardsOrForwards();
const t = document.startViewTransition({
update: updateTheDOMSomehow,
types: ['slide', direction],
});
Om op deze typen te reageren, gebruikt u de selector :active-view-transition-type() . Geef het type waarop u zich wilt richten door aan de selector. Hiermee kunt u de stijlen van meerdere weergaveovergangen van elkaar gescheiden houden, zonder dat de declaraties van de ene de declaraties van de andere beïnvloeden.
Omdat typen alleen van toepassing zijn bij het vastleggen of uitvoeren van de overgang, kunt u de selector gebruiken om een view-transition-name in te stellen – of te verwijderen – op een element, maar alleen voor de weergaveovergang met dat type.
/* 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;
}
}
In de volgende pagineringsdemo schuift de pagina-inhoud naar voren of naar achteren, afhankelijk van het paginanummer waarnaar u navigeert. Het type wordt bepaald bij een klik, waarna het wordt doorgegeven aan document.startViewTransition .
Om een actieve weergaveovergang te selecteren, ongeacht het type, kunt u in plaats daarvan de pseudo-klasse selector :active-view-transition gebruiken.
html:active-view-transition {
…
}
Beheer meerdere weergaveovergangsstijlen met een klassenaam op de basis van de weergaveovergang.
Soms vereist de overgang van het ene type weergave naar het andere een specifiek aangepaste overgang. Of, een 'terug'-navigatie moet anders zijn dan een 'vooruit'-navigatie.
Voordat overgangstypen bestonden, werd er in deze gevallen tijdelijk een klassenaam aan de overgangsroot toegekend. Bij het aanroepen van document.startViewTransition is deze overgangsroot het <html> -element, dat toegankelijk is via document.documentElement in 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');
}
Om de klassen te verwijderen nadat de overgang is voltooid, gebruikt dit voorbeeld transition.finished , een promise die wordt opgelost zodra de overgang de eindtoestand heeft bereikt. Andere eigenschappen van dit object worden beschreven in de API-referentie .
Nu kun je die klassenaam in je CSS gebruiken om de overgang aan te passen:
/* '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;
}
Net als bij mediaqueries kan de aanwezigheid van deze klassen ook worden gebruikt om te wijzigen welke elementen een view-transition-name krijgen.
Voer overgangen uit zonder andere animaties te blokkeren.
Bekijk deze demo van een video met een overgangspositie:
Zag je er iets mis mee? Geen zorgen als dat niet zo is. Hier is het in slow motion te bekijken:
Tijdens de overgang lijkt de video even stil te staan, waarna de afspelende versie van de video geleidelijk verschijnt. Dit komt doordat ::view-transition-old(video) een screenshot is van de oude weergave, terwijl ::view-transition-new(video) een livebeeld is van de nieuwe weergave.
Je kunt dit oplossen, maar vraag jezelf eerst af of het de moeite waard is. Als je het 'probleem' niet zag toen de overgang op normale snelheid werd afgespeeld, zou ik er niet aan beginnen.
Als je het echt wilt oplossen, laat dan niet ::view-transition-old(video) zien, maar schakel direct over naar ::view-transition-new(video) . Je kunt dit doen door de standaardstijlen en -animaties te overschrijven:
::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;
}
En dat is alles!
Nu wordt de video tijdens de overgang afgespeeld.
Integratie met de Navigation API (en andere frameworks)
View-transities worden zo gespecificeerd dat ze kunnen worden geïntegreerd met andere frameworks of libraries. Als uw single-page application (SPA) bijvoorbeeld een router gebruikt, kunt u het update-mechanisme van de router aanpassen om de content bij te werken met behulp van een view-transitie.
In het volgende codefragment, afkomstig uit deze pagineringsdemo, wordt de interceptiehandler van de Navigation API aangepast om document.startViewTransition aan te roepen wanneer weergaveovergangen worden ondersteund.
navigation.addEventListener("navigate", (e) => {
// Don't intercept if not needed
if (shouldNotIntercept(e)) return;
// Intercept the navigation
e.intercept({
handler: async () => {
// Fetch the new content
const newContent = await fetchNewContent(e.destination.url, {
signal: e.signal,
});
// The UA does not support View Transitions, or the UA
// already provided a Visual Transition by itself (e.g. swipe back).
// In either case, update the DOM directly
if (!document.startViewTransition || e.hasUAVisualTransition) {
setContent(newContent);
return;
}
// Update the content using a View Transition
const t = document.startViewTransition(() => {
setContent(newContent);
});
}
});
});
Sommige, maar niet alle, browsers bieden een eigen overgang aan wanneer de gebruiker een veegbeweging maakt om te navigeren. In dat geval moet u geen eigen weergaveovergang activeren, omdat dit tot een slechte of verwarrende gebruikerservaring zou leiden. De gebruiker zou dan twee overgangen zien – één van de browser en één van u – die na elkaar worden uitgevoerd.
Het is daarom aan te raden te voorkomen dat een weergaveovergang start wanneer de browser zelf een visuele overgang heeft verzorgd. Om dit te bereiken, controleert u de waarde van de eigenschap hasUAVisualTransition van het NavigateEvent exemplaar. Deze eigenschap is ingesteld op true wanneer de browser een visuele overgang heeft verzorgd. Deze eigenschap hasUIVisualTransition bestaat ook voor PopStateEvent exemplaren.
In het vorige codefragment wordt bij de controle of de weergaveovergang moet worden uitgevoerd rekening gehouden met deze eigenschap. Wanneer er geen ondersteuning is voor weergaveovergangen binnen hetzelfde document, of wanneer de browser al een eigen overgang heeft geboden, wordt de weergaveovergang overgeslagen.
if (!document.startViewTransition || e.hasUAVisualTransition) {
setContent(newContent);
return;
}
In de volgende opname veegt de gebruiker om terug te gaan naar de vorige pagina. De opname links bevat geen controle op de hasUAVisualTransition -vlag. De opname rechts bevat deze controle wel, waardoor de handmatige weergaveovergang wordt overgeslagen omdat de browser een visuele overgang biedt.
hasUAVisualTransitionAnimeren met JavaScript
Tot nu toe zijn alle overgangen gedefinieerd met CSS, maar soms is CSS niet genoeg:
Een aantal onderdelen van deze overgang kan niet uitsluitend met CSS worden gerealiseerd:
- De animatie start vanaf de kliklocatie.
- De animatie eindigt met een cirkel die een straal heeft tot aan de verste hoek. Hopelijk wordt dit in de toekomst wel mogelijk met CSS.
Gelukkig kun je overgangen maken met behulp van de 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)',
}
);
});
}
Dit voorbeeld maakt gebruik van transition.ready , een promise die wordt opgelost zodra de overgangspseudo-elementen succesvol zijn aangemaakt. Andere eigenschappen van dit object worden beschreven in de API-referentie .
Overgangen als verbetering
De View Transition API is ontworpen om een DOM-wijziging te 'verpakken' en er een overgang voor te creëren. De overgang moet echter worden beschouwd als een verbetering; uw app mag dus niet in een 'foutstatus' terechtkomen als de DOM-wijziging slaagt, maar de overgang mislukt. Idealiter zou de overgang niet moeten mislukken, maar als dat wel gebeurt, mag dit de rest van de gebruikerservaring niet verstoren.
Om overgangen als een verbetering te beschouwen, moet je ervoor zorgen dat je overgangsbeloftes niet gebruikt op een manier die ervoor zorgt dat je app een foutmelding geeft als de overgang mislukt.
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)', } ); }
Het probleem met dit voorbeeld is dat switchView() een foutmelding geeft als de overgang de ready status niet kan bereiken, maar dat betekent niet dat de weergave niet correct is overgeschakeld. De DOM is mogelijk wel succesvol bijgewerkt, maar er waren dubbele view-transition-name , waardoor de overgang werd overgeslagen.
In plaats van:
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 } }
Dit voorbeeld gebruikt transition.updateCallbackDone om te wachten op de DOM-update en om de actie te weigeren als deze mislukt. switchView weigert niet langer als de overgang mislukt, maar wordt opgelost wanneer de DOM-update is voltooid en weigert de actie als deze mislukt.
Als je wilt dat switchView wordt voltooid wanneer de nieuwe weergave 'stabiel' is, oftewel wanneer een geanimeerde overgang is voltooid of naar het einde is overgeslagen, vervang dan transition.updateCallbackDone door transition.finished .
Geen polyestervulling, maar…
Dit is geen eenvoudige functionaliteit om te polyfillen. Deze hulpfunctie maakt het echter een stuk gemakkelijker in browsers die geen view transitions ondersteunen:
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) {
return unsupported('skipTransition was set to true');
}
if (!document.startViewTransition) {
return unsupported('Same-Document View Transitions are not supported in this browser');
}
if (!ViewTransition || !("types" in ViewTransition.prototype)) {
return unsupported('View Transitions with types are not supported in this browser');
}
const transition = document.startViewTransition({
update,
types,
});
return transition;
}
En het kan als volgt worden gebruikt:
function spaNavigate(data) {
const types = isBackNavigation ? ['back-transition'] : [];
const transition = transitionHelper({
update() {
updateTheDOMSomehow(data);
},
types,
});
// …
}
In browsers die geen weergaveovergangen ondersteunen, wordt updateDOM nog steeds aangeroepen, maar er vindt geen geanimeerde overgang plaats.
Je kunt ook classNames opgeven die tijdens de overgang aan <html> worden toegevoegd, waardoor het gemakkelijker wordt om de overgang aan te passen aan het type navigatie .
Je kunt ook true doorgeven aan skipTransition als je geen animatie wilt, zelfs niet in browsers die weergaveovergangen ondersteunen. Dit is handig als gebruikers op je site de voorkeur geven aan het uitschakelen van overgangen.
Werken met frameworks
Als je werkt met een bibliotheek of framework dat DOM-wijzigingen abstraheert, is het lastige om te weten wanneer de DOM-wijziging voltooid is. Hier volgen een aantal voorbeelden, waarin de bovenstaande helperfunctie wordt gebruikt, in verschillende frameworks.
- React — de sleutel hier is
flushSync, waarmee een reeks statuswijzigingen synchroon wordt toegepast. Ja, er is een grote waarschuwing over het gebruik van die API, maar Dan Abramov verzekert me dat het in dit geval gepast is. Zoals gebruikelijk bij React en asynchrone code, moet je er bij het gebruik van de verschillende promises die doorstartViewTransitionworden geretourneerd, voor zorgen dat je code met de juiste status wordt uitgevoerd. - In Vue.js is
nextTickhier de sleutel, die wordt uitgevoerd zodra de DOM is bijgewerkt. - Svelte is erg vergelijkbaar met Vue, maar de methode om op de volgende wijziging te wachten is
tick. - Let op — de sleutel hier is de
this.updateComplete-promise binnen componenten, die wordt vervuld zodra de DOM is bijgewerkt. - Angular — de sleutel hier is
applicationRef.tick, waarmee openstaande DOM-wijzigingen worden doorgestuurd. Vanaf Angular versie 17 kun jewithViewTransitionsgebruiken, dat bij@angular/routerwordt geleverd .
API-referentie
-
const viewTransition = document.startViewTransition(update) Start een nieuwe
ViewTransition.updateis een functie die wordt aangeroepen zodra de huidige status van het document is vastgelegd.Als de belofte die door
updateCallbackwordt geretourneerd, wordt vervuld, begint de overgang in het volgende frame. Als de belofte die doorupdateCallbackwordt geretourneerd, wordt afgewezen, wordt de overgang afgebroken.-
const viewTransition = document.startViewTransition({ update, types }) Start een nieuwe
ViewTransitionmet de opgegeven typen.updatewordt aangeroepen zodra de huidige status van het document is vastgelegd.typesstelt de actieve typen in voor de overgang bij het vastleggen of uitvoeren van de overgang. Deze is aanvankelijk leeg. ZieviewTransition.typesverderop voor meer informatie.
Instantieleden van ViewTransition :
-
viewTransition.updateCallbackDone Een belofte die wordt vervuld wanneer de belofte die door
updateCallbackwordt geretourneerd, wordt vervuld, of wordt afgewezen wanneer deze wordt afgewezen.De View Transition API verpakt een DOM-wijziging en creëert een overgang. Soms is het echter niet belangrijk of de overgangsanimatie slaagt of mislukt, maar wilt u alleen weten of en wanneer de DOM-wijziging plaatsvindt. Voor dat specifieke geval is
updateCallbackDone.-
viewTransition.ready Een belofte die wordt ingelost zodra de pseudo-elementen voor de overgang zijn aangemaakt en de animatie op het punt staat te beginnen.
Het wordt afgewezen als de overgang niet kan beginnen. Dit kan te wijten zijn aan een verkeerde configuratie, zoals dubbele
view-transition-name, of alsupdateCallbackeen afgewezen promise retourneert.Dit is handig voor het animeren van overgangs-pseudo-elementen met JavaScript .
-
viewTransition.finished Een belofte die wordt ingelost zodra de eindtoestand volledig zichtbaar en interactief is voor de gebruiker.
Het wordt alleen afgewezen als
updateCallbackeen afgewezen promise retourneert, omdat dit aangeeft dat de eindtoestand niet is gecreëerd.Als een overgang niet start of wordt overgeslagen tijdens de overgang, wordt de eindtoestand alsnog bereikt, waardoor de status '
finishedvoldoet.-
viewTransition.types Een
Set-achtig object dat de typen van de actieve weergaveovergang bevat. Om de items te manipuleren, gebruikt u de instantie-methodenclear(),add()endelete().Om in CSS op een specifiek type te reageren, gebruik je de pseudo-klasse selector
:active-view-transition-type(type)op de overgangsroot.De gegevenstypen worden automatisch opgeruimd wanneer de overgang tussen weergaven is voltooid.
-
viewTransition.skipTransition() Sla het animatiegedeelte van de overgang over.
Hierdoor wordt de aanroep
updateCallbackniet overgeslagen, aangezien de DOM-wijziging losstaat van de overgang.
Standaardstijl en overgangsreferentie
-
::view-transition - Het root pseudo-element dat de viewport vult en elke
::view-transition-groupbevat. -
::view-transition-group Perfecte locatie.
widthenheightvan de overgangen tussen de 'voor' en 'na' toestanden.Overgangen
transformtussen het 'voor' en 'na' weergavegebied.-
::view-transition-image-pair Perfect gepositioneerd om de groep aan te vullen.
Heeft
isolation: isolateom het effect van demix-blend-modeop de oude en nieuwe weergaven te beperken.-
::view-transition-newen::view-transition-old Precies linksboven op de verpakking.
Vult 100% van de groepsbreedte, maar heeft een automatische hoogte, waardoor de beeldverhouding behouden blijft in plaats van de hele groep te vullen.
Heeft
mix-blend-mode: plus-lightervoor een echte crossfade.De oude weergave verandert van
opacity: 1naaropacity: 0De nieuwe weergave verandert vanopacity: 0naaropacity: 1.
Feedback
Feedback van ontwikkelaars wordt altijd op prijs gesteld. Dien hiervoor een issue in bij de CSS Working Group op GitHub met suggesties en vragen. Voeg [css-view-transitions] toe aan het begin van je issue.
Mocht je een bug tegenkomen, meld deze dan bij Chromium .