Met de View Transition API kunt u de DOM eenvoudig in één stap wijzigen en tegelijkertijd een geanimeerde overgang tussen de twee statussen creëren. Het is beschikbaar in Chrome 111+.
Waarom hebben we deze functie nodig?
Paginaovergangen zien er niet alleen geweldig uit, ze communiceren ook de richting van de stroom en maken duidelijk welke elementen van pagina tot pagina gerelateerd zijn. Ze kunnen zelfs optreden tijdens het ophalen van gegevens, wat leidt tot een snellere perceptie van de prestaties.
Maar we hebben al animatietools op internet, zoals CSS-overgangen , CSS-animaties en de Web Animation API , dus waarom hebben we iets nieuws nodig om dingen te verplaatsen?
De waarheid is dat staatstransities moeilijk zijn, zelfs met de instrumenten die we al hebben.
Zelfs zoiets als een simpele cross-fade houdt in dat beide toestanden tegelijkertijd aanwezig zijn. Dat brengt uitdagingen op het gebied van bruikbaarheid met zich mee, zoals het omgaan met extra interactie op het uitgaande element. Voor gebruikers van hulpmiddelen is er ook een periode waarin zowel de voor- als de na-status zich tegelijkertijd in de DOM bevinden, en dingen door de boom kunnen bewegen op een manier die visueel prima is, maar er gemakkelijk voor kan zorgen dat de leespositie en focus verslechteren. verdwaald zijn.
Het omgaan met statuswijzigingen is vooral een uitdaging als de twee statussen verschillen in scrollpositie. En als een element van de ene container naar de andere beweegt, kunt u problemen ondervinden met overflow: hidden
en andere vormen van clipping, wat betekent dat u uw CSS moet herstructureren om het gewenste effect te krijgen.
Het is niet onmogelijk, het is gewoon heel moeilijk .
Overgangen bekijken biedt u een eenvoudigere manier, doordat u uw DOM-wijziging kunt doorvoeren zonder enige overlap tussen staten, maar een overgangsanimatie tussen de staten kunt maken met behulp van momentopnamen.
Hoewel de huidige implementatie zich richt op single page apps (SPA's), zal deze functie bovendien worden uitgebreid om overgangen mogelijk te maken tussen het laden van volledige pagina's, wat momenteel onmogelijk is.
Standaardisatiestatus
De functie wordt binnen de W3C CSS Working Group ontwikkeld als conceptspecificatie .
Zodra we tevreden zijn met het API-ontwerp, starten we de processen en controles die nodig zijn om deze functie naar stable te verzenden.
Feedback van ontwikkelaars is erg belangrijk, dus plaats problemen op GitHub met suggesties en vragen.
De eenvoudigste overgang: een cross-fade
De standaard View Transition is een cross-fade, dus het dient als een leuke introductie tot de 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));
}
Waar updateTheDOMSomehow
de DOM naar de nieuwe staat verandert. Dat kan op elke gewenste manier: elementen toevoegen/verwijderen, klassennamen wijzigen, stijlen wijzigen… het maakt niet uit.
En zomaar, pagina's vervagen:
Oké, een cross-fade is niet zo indrukwekkend. Gelukkig kunnen overgangen worden aangepast, maar voordat we daar aan toekomen, moeten we begrijpen hoe deze fundamentele cross-fade werkte.
Hoe deze transities werken
Het codevoorbeeld van bovenaf nemen:
document.startViewTransition(() => updateTheDOMSomehow(data));
Wanneer .startViewTransition()
wordt aangeroepen, legt de API de huidige status van de pagina vast. Dit omvat het maken van een screenshot.
Zodra dat is voltooid, wordt de callback die wordt doorgegeven aan .startViewTransition()
aangeroepen. Dat is waar de DOM wordt gewijzigd. Vervolgens legt de API de nieuwe status van de pagina vast.
Zodra de status is vastgelegd, construeert de API een boom met pseudo-elementen zoals deze:
::view-transition
└─ ::view-transition-group(root)
└─ ::view-transition-image-pair(root)
├─ ::view-transition-old(root)
└─ ::view-transition-new(root)
De ::view-transition
bevindt zich in een overlay, over al het andere op de pagina. Dit is handig als u 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 'vervangen inhoud' (zoals een <img>
).
De oude weergave animeert van opacity: 1
naar opacity: 0
, terwijl de nieuwe weergave animeert van opacity: 0
naar opacity: 1
, waardoor een cross-fade ontstaat.
Alle animaties worden uitgevoerd met behulp van CSS-animaties, zodat ze kunnen worden aangepast met CSS.
Eenvoudig maatwerk
Alle bovenstaande pseudo-elementen kunnen worden getarget met CSS, en aangezien de animaties worden gedefinieerd met behulp van CSS, kunt u ze aanpassen met behulp van bestaande CSS-animatie-eigenschappen. Bijvoorbeeld:
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 5s;
}
Met die ene verandering is de vervaging nu erg langzaam:
Oké, dat is nog steeds niet indrukwekkend. Laten we in plaats daarvan de gedeelde asovergang van Material Design implementeren:
@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 hier is het resultaat:
Meerdere elementen overzetten
In de vorige demo was de hele pagina betrokken bij de gedeelde asovergang. Dat werkt voor het grootste deel van de pagina, maar het lijkt niet helemaal juist voor de kop, omdat deze naar buiten schuift om vervolgens weer naar binnen te schuiven.
Om dit te voorkomen, kunt u de koptekst uit de rest van de pagina halen, zodat deze afzonderlijk kan worden geanimeerd. Dit wordt gedaan door een view-transition-name
aan het element toe te wijzen.
.main-header {
view-transition-name: main-header;
}
De waarde van view-transition-name
kan zijn wat u maar wilt (behalve none
, wat betekent dat er geen transitienaam is). Het wordt gebruikt om het element tijdens de overgang op unieke wijze te identificeren.
En het resultaat daarvan:
Nu blijft de header op zijn plaats en vervaagt hij.
Die CSS-declaratie zorgde ervoor dat de boom met pseudo-elementen 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 transitiegroepen. Eén voor de header en één voor de rest. Deze kunnen onafhankelijk worden getarget met CSS en met verschillende overgangen. Hoewel in dit geval main-header
de standaardovergang had, wat een cross-fade is.
Nou, oké, de standaardovergang is niet alleen een cross-fade, de ::view-transition-group
gaat ook over:
- Positioneren en transformeren (via een
transform
) - Breedte
- Hoogte
Dat deed er tot nu toe niet toe, omdat de header dezelfde grootte heeft en dezelfde positie heeft aan beide zijden van de DOM-wijziging. Maar we kunnen ook de tekst in de koptekst 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 verkleint de pijl-terug de grootte van het koptekstelement, terwijl we willen dat deze op beide pagina's dezelfde grootte heeft.
Dus nu hebben we drie delen om mee te spelen:
::view-transition
├─ ::view-transition-group(root)
│ └─ …
├─ ::view-transition-group(main-header)
│ └─ …
└─ ::view-transition-group(main-header-text)
└─ …
Maar nogmaals, gewoon met de standaardinstellingen gaan:
Nu schuift de koptekst een beetje bevredigend over om ruimte te maken voor de terugknop.
Debuggen van overgangen
Omdat weergaveovergangen bovenop CSS-animaties zijn gebouwd, is het deelvenster Animaties in Chrome DevTools ideaal voor het opsporen van fouten in overgangen.
Met behulp van het deelvenster Animaties kunt u de volgende animatie pauzeren en vervolgens heen en weer door de animatie bladeren. Tijdens deze periode zijn de overgangspseudo-elementen te vinden in het paneel Elementen .
Overgangselementen hoeven niet hetzelfde DOM-element te zijn
Tot nu toe hebben we view-transition-name
gebruikt om afzonderlijke overgangselementen voor de koptekst en de tekst in de koptekst te maken. Dit zijn conceptueel hetzelfde element voor en na de DOM-wijziging, maar je kunt overgangen maken waar dat niet het geval is.
De hoofdvideo-insluiting 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, 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 miniatuur gaat nu over in de hoofdafbeelding. Hoewel het conceptueel (en letterlijk) verschillende elementen zijn, behandelt de transitie-API ze als hetzelfde omdat ze dezelfde view-transition-name
delen.
De echte code hiervoor is iets ingewikkelder dan het eenvoudige voorbeeld hierboven, omdat deze ook de overgang terug naar de miniatuurpagina afhandelt. Zie de bron voor de volledige implementatie.
Aangepaste in- en uitstapovergangen
Kijk naar dit voorbeeld:
De zijbalk maakt deel uit van de overgang:
.sidebar {
view-transition-name: sidebar;
}
Maar in tegenstelling tot de koptekst 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 zich echter alleen op de nieuwe pagina bevindt, zal het pseudo-element ::view-transition-old(sidebar)
er niet zijn. Omdat er geen 'oude' afbeelding voor de zijbalk is, heeft het afbeeldingspaar alleen een ::view-transition-new(sidebar)
. Op dezelfde manier, als de zijbalk zich alleen op de oude pagina bevindt, heeft het afbeeldingspaar alleen een ::view-transition-old(sidebar)
.
In de bovenstaande demo gaat de zijbalk anders over, afhankelijk van of deze in beide staten binnenkomt, verlaat of aanwezig is. Het komt binnen door van rechts te schuiven en naar binnen te faden, het komt naar buiten door naar rechts te schuiven en uit te faden, en het blijft op zijn plaats als het in beide toestanden aanwezig is.
Om specifieke entry- en exit-overgangen te maken, kunt u de :only-child
pseudo-klasse gebruiken om het oude/nieuwe pseudo-element te targeten wanneer dit het enige kind 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 wanneer de zijbalk in beide staten aanwezig is, omdat de standaard perfect is.
Asynchrone DOM-updates en wachten op inhoud
De callback die wordt doorgegeven aan .startViewTransition()
kan een belofte retourneren, die asynchrone DOM-updates mogelijk maakt en wacht tot belangrijke inhoud gereed is.
document.startViewTransition(async () => {
await something;
await updateTheDOMSomehow();
await somethingElse;
});
De transitie zal pas beginnen als de belofte wordt waargemaakt. Gedurende deze tijd is de pagina bevroren, dus vertragingen moeten hier tot een minimum worden beperkt. Netwerkophaalacties moeten met name worden uitgevoerd voordat .startViewTransition()
wordt aangeroepen, terwijl de pagina nog steeds volledig interactief is, in plaats van ze uit te voeren als onderdeel van de callback .startViewTransition()
.
Als u besluit te wachten totdat afbeeldingen of lettertypen gereed zijn, zorg er dan voor dat u een agressieve time-out gebruikt:
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 u al heeft.
Haal het beste uit de inhoud die u al heeft
In het geval dat de miniatuur overgaat in een grotere afbeelding:
De standaardovergang is cross-fade, wat betekent dat de miniatuur kan cross-faden met een nog niet geladen volledige afbeelding.
Eén manier om dit aan te pakken is door te wachten tot de volledige afbeelding is geladen voordat u met de overgang begint. Idealiter zou dit worden gedaan voordat .startViewTransition()
wordt aangeroepen, zodat de pagina interactief blijft en er een spinner kan worden weergegeven om aan de gebruiker aan te geven dat dingen worden 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;
}
Nu vervaagt de miniatuur niet, maar bevindt deze zich gewoon onder de volledige afbeelding. Dit betekent dat als de nieuwe weergave niet is geladen, de miniatuur zichtbaar is tijdens de overgang. Dit betekent dat de overgang meteen kan beginnen en dat de volledige afbeelding op zijn eigen tijd kan worden geladen.
Dit zou niet werken als de nieuwe weergave transparantie zou bieden, maar in dit geval weten we dat dit niet het geval is, dus kunnen we deze optimalisatie doorvoeren.
Omgaan met veranderingen in de beeldverhouding
Handig genoeg waren alle overgangen tot nu toe naar elementen met dezelfde beeldverhouding, 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 voor- naar de na-grootte. De oude en nieuwe weergaven zijn 100% breed en automatisch hoog, wat betekent dat ze hun beeldverhouding behouden, ongeacht de groepsgrootte.
Dit is een goede standaard, maar in dit geval is dit niet wat we willen. 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 naarmate de breedte groter wordt, maar dat de volledige afbeelding wordt 'uitgesneden' wanneer deze overgaat van 1:1 naar 16:9.
De overgang wijzigen afhankelijk van de apparaatstatus
Mogelijk wilt u verschillende overgangen gebruiken op mobiel versus desktop, zoals dit voorbeeld, waarbij op mobiel een volledige dia vanaf de zijkant wordt uitgevoerd, maar een subtielere dia op desktop:
Dit kan worden bereikt met behulp van reguliere mediaquery's:
/* 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;
}
}
Mogelijk wilt u ook wijzigen aan welke elementen u een view-transition-name
toewijst, afhankelijk van overeenkomende mediaquery's.
Reageren op de voorkeur voor 'verminderde beweging'
Gebruikers kunnen via hun besturingssysteem aangeven dat ze minder beweging willen, en die voorkeur wordt via CSS zichtbaar .
U kunt ervoor kiezen om eventuele overgangen voor deze gebruikers te voorkomen:
@media (prefers-reduced-motion) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}
Een voorkeur voor 'verminderde beweging' betekent echter niet dat de gebruiker geen beweging wil. In plaats van het bovenstaande zou je een subtielere animatie kunnen kiezen, maar wel één die nog steeds de relatie tussen de elementen en de gegevensstroom tot uitdrukking brengt.
De overgang wijzigen afhankelijk van het type navigatie
Soms moet een navigatie van het ene type pagina naar het andere een specifiek op maat gemaakte overgang hebben. Of een 'terug'-navigatie moet anders zijn dan een 'vooruit'-navigatie.
De beste manier om deze gevallen af te handelen is door een klassenaam in te stellen op <html>
, ook wel het documentelement genoemd:
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');
}
In dit voorbeeld wordt transition.finished
gebruikt, een belofte die wordt vervuld zodra de transitie de eindstatus heeft bereikt. Andere eigenschappen van dit object worden behandeld in de API-referentie .
Nu kunt u die klassenaam in uw CSS gebruiken om de overgang te wijzigen:
/* '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 mediaquery's kan de aanwezigheid van deze klassen ook worden gebruikt om te wijzigen welke elementen een view-transition-name
krijgen.
Overstappen zonder andere animaties te bevriezen
Bekijk deze demo van een video-overgangspositie:
Heb je er iets verkeerds aan gezien? Maak je geen zorgen als je dat niet hebt gedaan. Hier wordt het vertraagd:
Tijdens de overgang lijkt de video vast te lopen, waarna de afgespeelde versie van de video verdwijnt. Dit komt omdat de ::view-transition-old(video)
een screenshot is van de oude weergave, terwijl de ::view-transition-new(video)
is een live beeld van de nieuwe weergave.
Je kunt dit oplossen, maar vraag jezelf eerst af of het de moeite waard is om het te repareren. Als je het 'probleem' niet zou zien toen de overgang op normale snelheid speelde, zou ik niet de moeite nemen om deze te veranderen.
Als je het echt wilt repareren, laat dan niet de ::view-transition-old(video)
; schakel direct naar de ::view-transition-new(video)
. U 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 het!
Nu wordt de video tijdens de overgang afgespeeld.
Animeren met JavaScript
Tot nu toe zijn alle overgangen gedefinieerd met behulp van CSS, maar soms is CSS niet voldoende:
Een aantal onderdelen van deze transitie kunnen niet alleen met CSS worden bereikt:
- De animatie begint vanaf de kliklocatie.
- De animatie eindigt met de cirkel met een straal naar de verste hoek. Hoewel dit hopelijk in de toekomst mogelijk zal zijn met CSS.
Gelukkig kun je overgangen maken met 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)',
}
);
});
}
In dit voorbeeld wordt transition.ready
gebruikt, een belofte die wordt opgelost zodra de pseudo-elementen voor de transitie met succes zijn gemaakt. Andere eigenschappen van dit object worden behandeld in de API-referentie .
Overgangen als verbetering
De View Transition API is ontworpen om een DOM-wijziging 'in te pakken' en er een transitie voor te maken. De overgang moet echter worden behandeld als een verbetering, omdat uw app geen 'fout'-status mag krijgen als de DOM-wijziging slaagt, maar de overgang mislukt. Idealiter mag de transitie niet mislukken, maar als dat wel het geval is, mag dit de rest van de gebruikerservaring niet verstoren.
Als u overgangen als een verbetering wilt beschouwen, moet u ervoor zorgen dat u de overgangsbeloften niet op een manier gebruikt die ervoor zou zorgen dat uw app een foutmelding krijgt 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()
zal afwijzen als de transitie de status ready
niet kan bereiken, maar dat betekent niet dat de weergave er niet in is geslaagd om te schakelen. De DOM is mogelijk succesvol bijgewerkt, maar er waren dubbele view-transition-name
, dus 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 } }
In dit voorbeeld wordt transition.updateCallbackDone
gebruikt om te wachten op de DOM-update en om te weigeren als deze mislukt. switchView
weigert niet langer als de transitie mislukt, maar wordt opgelost wanneer de DOM-update is voltooid en wordt afgewezen als deze mislukt.
Als u wilt switchView
wordt opgelost wanneer de nieuwe weergave is 'gesetteld', zoals wanneer een geanimeerde overgang is voltooid of naar het einde is overgeslagen, vervangt u transition.updateCallbackDone
door transition.finished
.
Geen polyfill, maar...
Ik denk niet dat deze functie op een nuttige manier kan worden ingevuld, maar ik ben blij dat ik ongelijk krijg!
Deze helperfunctie maakt het echter veel eenvoudiger in browsers die geen weergaveovergangen ondersteunen:
function transitionHelper({
skipTransition = false,
classNames = [],
updateDOM,
}) {
if (skipTransition || !document.startViewTransition) {
const updateCallbackDone = Promise.resolve(updateDOM()).then(() => {});
return {
ready: Promise.reject(Error('View transitions unsupported')),
updateCallbackDone,
finished: updateCallbackDone,
skipTransition: () => {},
};
}
document.documentElement.classList.add(...classNames);
const transition = document.startViewTransition(updateDOM);
transition.finished.finally(() =>
document.documentElement.classList.remove(...classNames)
);
return transition;
}
En het kan als volgt worden gebruikt:
function spaNavigate(data) {
const classNames = isBackNavigation ? ['back-transition'] : [];
const transition = transitionHelper({
classNames,
updateDOM() {
updateTheDOMSomehow(data);
},
});
// …
}
In browsers die View Transitions niet ondersteunen, wordt updateDOM
nog steeds aangeroepen, maar is er geen geanimeerde overgang.
U kunt ook enkele classNames
opgeven die u tijdens de transitie aan <html>
kunt toevoegen, waardoor het gemakkelijker wordt om de transitie te wijzigen, afhankelijk van het type navigatie .
U kunt ook true
doorgeven om skipTransition
slaan als u geen animatie wilt, zelfs in browsers die View Transitions ondersteunen. Dit is handig als uw site een gebruikersvoorkeur heeft om overgangen uit te schakelen.
Werken met kaders
Als je met een bibliotheek of raamwerk werkt dat DOM-wijzigingen abstraheert, is het lastig om te weten wanneer de DOM-wijziging voltooid is. Hier is een reeks voorbeelden, waarbij gebruik wordt gemaakt van de bovenstaande helper , in verschillende raamwerken.
- Reageren : de sleutel hier is
flushSync
, die een reeks statuswijzigingen synchroon toepast. Ja, er is een grote waarschuwing over het gebruik van die API, maar Dan Abramov verzekert mij dat dit in dit geval gepast is. Zoals gebruikelijk bij React en asynchrone code, zorg er bij het gebruik van de verschillende beloften die worden geretourneerd doorstartViewTransition
voor dat uw code in de juiste staat wordt uitgevoerd. - Vue.js —de sleutel hier is
nextTick
, die wordt vervuld zodra de DOM is bijgewerkt. - Svelte - lijkt erg op Vue, maar de methode om op de volgende wijziging te wachten is
tick
. - Lit : de sleutel hier is de
this.updateComplete
belofte binnen componenten, die wordt vervuld zodra de DOM is bijgewerkt. - Angular : de sleutel hier is
applicationRef.tick
, waarmee lopende DOM-wijzigingen worden gewist. Vanaf Angular versie 17 kunt uwithViewTransitions
gebruiken dat wordt meegeleverd met@angular/router
.
API-referentie
-
const viewTransition = document.startViewTransition(updateCallback)
Start een nieuwe
ViewTransition
.updateCallback
wordt aangeroepen zodra de huidige status van het document is vastgelegd.Wanneer de door
updateCallback
geretourneerde belofte wordt vervuld, begint de overgang in het volgende frame. Als de doorupdateCallback
geretourneerde belofte wordt afgewezen, wordt de transitie afgebroken.
Instantieleden van ViewTransition
:
-
viewTransition.updateCallbackDone
Een belofte die wordt nagekomen wanneer de door
updateCallback
geretourneerde belofte wordt nagekomen, of wordt afgewezen wanneer deze wordt afgewezen.De View Transition API verpakt een DOM-wijziging en creëert een transitie. Soms maakt het succes/mislukken van de overgangsanimatie u echter niets uit, maar wilt u gewoon weten of en wanneer de DOM-wijziging plaatsvindt.
updateCallbackDone
is voor dat gebruik.-
viewTransition.ready
Een belofte die werkelijkheid wordt zodra de pseudo-elementen voor de transitie zijn gecreëerd en de animatie op het punt staat te beginnen.
Het wijst het af als de transitie niet kan beginnen. Dit kan te wijten zijn aan een verkeerde configuratie, zoals dubbele
view-transition-name
, of alsupdateCallback
een afgewezen belofte retourneert.Dit is handig voor het animeren van de overgangspseudo-elementen met JavaScript .
-
viewTransition.finished
Een belofte die wordt waargemaakt zodra de eindtoestand volledig zichtbaar en interactief is voor de gebruiker.
Het wijst alleen af als
updateCallback
een afgewezen belofte retourneert, omdat dit aangeeft dat de eindstatus niet is gemaakt.Anders, als een transitie niet op gang komt, of wordt overgeslagen tijdens de transitie, wordt de eindtoestand nog steeds bereikt, dus
finished
is vervuld.-
viewTransition.skipTransition()
Sla het animatiegedeelte van de overgang over.
Hiermee wordt het aanroepen van
updateCallback
niet overgeslagen, omdat de DOM-wijziging losstaat van de overgang.
Standaardstijl en overgangsreferentie
-
::view-transition
- Het root-pseudo-element dat de viewport vult en elke
::view-transition-group
bevat. -
::view-transition-group
Absoluut gepositioneerd.
Overgangen
width
enheight
tussen de 'voor' en 'na' staten.Overgangen
transform
tussen de 'voor' en 'na' viewport-ruimte quad.-
::view-transition-image-pair
Absoluut gepositioneerd om de groep te vullen.
Heeft
isolation: isolate
om het effect van deplus-lighter
overvloeimodus op de oude en nieuwe weergaven te beperken.-
::view-transition-new
en::view-transition-old
Absoluut linksboven op de verpakking gepositioneerd.
Vult 100% van de groepsbreedte, maar heeft een automatische hoogte, zodat de beeldverhouding behouden blijft in plaats van de groep te vullen.
Heeft
mix-blend-mode: plus-lighter
om een echte cross-fade mogelijk te maken.De oude weergave gaat over van
opacity: 1
naaropacity: 0
. De nieuwe weergave gaat over vanopacity: 0
naaropacity: 1
.
Feedback
Feedback van ontwikkelaars is in deze fase erg belangrijk, dus plaats problemen op GitHub met suggesties en vragen.