Gepubliceerd: 17 aug. 2021, Laatst bijgewerkt: 25 sep. 2024
Wanneer een weergaveovergang op één document wordt uitgevoerd, wordt dit een 'same-document view transition' genoemd. Dit is meestal het geval in single-page applicaties (SPA's) waar JavaScript wordt gebruikt om de DOM bij te werken. Overgangen naar dezelfde documentweergave worden in Chrome ondersteund vanaf Chrome 111.
Om een overgang naar dezelfde documentweergave 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 optie wordt aangeroepen, maakt de browser automatisch momentopnamen van alle elementen waaraan de CSS-eigenschap view-transition-name
is gedeclareerd.
Vervolgens wordt de meegegeven callback uitgevoerd die de DOM bijwerkt, waarna er snapshots worden gemaakt van de nieuwe status.
Deze snapshots worden vervolgens gerangschikt in een boomstructuur van pseudo-elementen en geanimeerd met behulp van CSS-animaties. Paren snapshots van de oude en nieuwe staat gaan vloeiend over van hun oude positie en grootte naar hun nieuwe locatie, terwijl hun inhoud overvloeit. Je kunt de animaties desgewenst aanpassen met CSS.
De standaardovergang: Cross-fade
De standaardweergaveovergang is een cross-fade, wat een mooie 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));
}
Waar updateTheDOMSomehow
de DOM naar de nieuwe staat verandert. Dat kan op elke gewenste manier. Je kunt bijvoorbeeld elementen toevoegen of verwijderen, klassennamen wijzigen of stijlen aanpassen.
En zo gaan de pagina's in elkaar over:
Oké, een cross-fade is niet zo indrukwekkend. Gelukkig kun je overgangen aanpassen, maar eerst moet je begrijpen hoe deze basis-cross-fade 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 het maken van een momentopname.
Zodra dit voltooid is, wordt de callback aangeroepen die aan .startViewTransition()
is doorgegeven. 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-elementboom 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 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 animeert van opacity: 1
naar opacity: 0
, terwijl de nieuwe weergave animeert van opacity: 0
naar opacity: 1
, waardoor een overgang ontstaat.
Alle animaties worden uitgevoerd met behulp van CSS-animaties en kunnen dus met CSS worden aangepast.
Pas de overgang aan
Alle pseudo-elementen van de weergaveovergang kunnen met CSS worden aangestuurd. Omdat de animaties met CSS worden gedefinieerd, kunt u ze aanpassen met bestaande CSS-animatie-eigenschappen. Bijvoorbeeld:
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 5s;
}
Door die ene verandering verloopt het vervagen nu 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:
Overgang van meerdere elementen
In de vorige demo is de hele pagina betrokken bij de gedeelde asovergang. Dat werkt voor het grootste deel van de pagina, maar het lijkt niet helemaal te passen bij de kop, omdat die naar buiten schuift om er vervolgens weer in te schuiven.
Om dit te voorkomen, kunt u de header uit de rest van de pagina halen, zodat deze afzonderlijk kan worden geanimeerd. Dit doet u 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 elke gewenste waarde zijn (behalve none
, wat betekent dat er geen overgangsnaam is). Deze waarde wordt gebruikt om het element in de overgang eenduidig te identificeren.
En het resultaat daarvan:
Nu blijft de header op zijn plaats en vervaagt deze.
Die CSS-declaratie zorgde ervoor dat de pseudo-elementboom 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: één voor de header en één voor de rest. Deze kunnen onafhankelijk van elkaar met CSS worden aangestuurd en verschillende overgangen krijgen. In dit geval bleef de standaardovergang, een cross-fade, echter behouden voor main-header
.
Oké, de standaardovergang is niet alleen een cross-fade, de ::view-transition-group
maakt ook overgangen:
- Positie en transformatie (met behulp van een
transform
) - Breedte
- Hoogte
Dat maakte tot nu toe niet uit, omdat de header dezelfde grootte heeft en de positie aan beide kanten van de DOM verandert. 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 over de resterende breedte. Zonder deze functie verkleint de pijl-terug de grootte van het headertekstelement, in plaats van dezelfde grootte op beide pagina's.
Nu hebben we 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, ik blijf bij de standaardinstellingen:
Nu schuift de koptekst op een prettige manier over de pagina om ruimte te maken voor de terugknop.
Animeer meerdere pseudo-elementen op dezelfde manier met view-transition-class
Browser Support
Stel je voor dat je een weergaveovergang hebt 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 activeert.
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 uitbreiden. Niet echt schaalbaar.
De view-transition-class
kan worden gebruikt in de pseudo-elementen van de view-overgang 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 kaartvoorbeeld 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 de toegevoegde of verwijderde.Debug-overgangen
Omdat weergaveovergangen worden opgebouwd op basis van CSS-animaties, is het paneel Animaties in Chrome DevTools ideaal voor het opsporen van fouten in overgangen.
Met het paneel Animaties kun je de volgende animatie pauzeren en vervolgens heen en weer door de animatie scrollen. De pseudo-elementen voor de overgangen vind je in het paneel Elementen .
Overgangselementen hoeven niet hetzelfde DOM-element te zijn
Tot nu toe hebben we view-transition-name
gebruikt om aparte overgangselementen te maken voor de header en de tekst in de header. Dit zijn in principe dezelfde elementen vóór en na de DOM-wijziging, maar je kunt overgangen maken waar dat niet het geval is.
U kunt bijvoorbeeld aan de belangrijkste video-embed een view-transition-name
geven:
.full-embed {
view-transition-name: full-embed;
}
Wanneer u vervolgens op de miniatuur klikt, kunt u deze dezelfde view-transition-name
geven, maar dan 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 voor deze overgang is iets ingewikkelder dan in het vorige voorbeeld, omdat deze ook de overgang terug naar de thumbnailpagina verwerkt. Zie de broncode voor de volledige implementatie.
Aangepaste in- en uitgangsovergangen
Kijk eens naar dit voorbeeld:
De zijbalk is onderdeel 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 statussen een zijbalk hebben, zien de overgangs-pseudo-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 afbeeldingenpaar alleen een ::view-transition-new(sidebar)
hebben. Evenzo, als de zijbalk alleen op de oude pagina staat, zal het afbeeldingenpaar alleen een ::view-transition-old(sidebar)
hebben.
In de vorige demo verloopt de overgang van de zijbalk anders, afhankelijk van of deze in beide toestanden verschijnt, verdwijnt of aanwezig is. De zijbalk verschijnt door van rechts te schuiven en in te faden, verdwijnt door naar rechts te schuiven en uit te faden, en blijft op zijn plaats wanneer deze in beide toestanden aanwezig is.
Om specifieke in- en uitgangsovergangen te maken, kunt u de pseudo-klasse :only-child
gebruiken om de oude of nieuwe pseudo-elementen te targeten wanneer dit het enige onderliggende element 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 toestanden aanwezig is, aangezien de standaardinstelling perfect is.
Async DOM-updates en wachten op inhoud
De callback die aan .startViewTransition()
wordt doorgegeven, kan een promise retourneren, die asynchrone DOM-updates toestaat en wacht tot belangrijke inhoud 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 hier tot een minimum worden beperkt. Netwerkophalingen moeten met name worden uitgevoerd vóór het aanroepen van .startViewTransition()
, terwijl de pagina nog volledig interactief is, in plaats van ze uit te voeren als onderdeel van de .startViewTransition()
callback.
Als u besluit te wachten tot afbeeldingen of lettertypen klaar 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)]);
});
Soms is het echter beter om de vertraging helemaal te vermijden en de content te gebruiken die u 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 cross-fade, wat betekent dat de miniatuur kan overvloeien met een nog niet geladen, volledige afbeelding.
Eén manier om dit aan te pakken is door te wachten tot de volledige afbeelding geladen is voordat de overgang begint. Idealiter zou dit gebeuren voordat .startViewTransition()
wordt aangeroepen, zodat de pagina interactief blijft en een spinner kan worden getoond om de gebruiker te laten weten dat er iets geladen wordt. 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 vervaagt nu niet meer, maar blijft gewoon onder de volledige afbeelding staan. Dit betekent dat, zelfs als de nieuwe weergave nog niet geladen is, de miniatuur gedurende de hele overgang zichtbaar blijft. Dit betekent dat de overgang direct kan starten en de volledige afbeelding op zijn eigen tempo 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.
Wijzigingen in beeldverhouding verwerken
Toevallig zijn alle overgangen tot nu toe naar elementen met dezelfde beeldverhouding gegaan, maar dat zal niet altijd het geval zijn. Wat als de miniatuur 1:1 is en de hoofdafbeelding 16:9?
In de standaardovergang animeert de groep van de voor- naar de na-grootte. De oude en nieuwe weergaven hebben 100% breedte van de groep en automatische hoogte, wat betekent dat ze hun beeldverhouding behouden, ongeacht de grootte van de groep.
Dit is een goede standaardinstelling, maar in dit geval is het 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 staan als de breedte toeneemt, maar dat de volledige afbeelding wordt 'ontsneden' bij de overgang van 1:1 naar 16:9.
Voor meer gedetailleerde informatie, zie Weergave-overgangen: Omgaan met wijzigingen in de beeldverhouding
Gebruik mediaquery's om overgangen voor verschillende apparaatstatussen te wijzigen
Het kan zijn dat u verschillende overgangen wilt gebruiken op mobiele apparaten en desktops, zoals in dit voorbeeld waarbij op mobiele apparaten een volledige slide vanaf de zijkant wordt uitgevoerd, maar op desktops een subtielere slide:
Dit kan worden bereikt met behulp van reguliere media queries:
/* 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 welke elementen u een view-transition-name
toewijst, afhankelijk van de overeenkomende media query's.
Reageer op de voorkeur 'verminderde beweging'
Gebruikers kunnen via hun besturingssysteem aangeven dat zij de voorkeur geven aan beperkte beweging. Deze voorkeur wordt vervolgens weergegeven in CSS .
U kunt ervoor kiezen om 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 'beperkte beweging' betekent echter niet dat de gebruiker geen beweging wil. In plaats van het voorgaande fragment zou je kunnen kiezen voor een subtielere animatie, die echter nog steeds de relatie tussen elementen en de datastroom weergeeft.
Meerdere weergave-overgangsstijlen verwerken met weergave-overgangstypen
Browser Support
Soms heeft een overgang van de ene naar de andere weergave een specifiek aangepaste overgang nodig. Bijvoorbeeld, wanneer u naar de volgende of vorige pagina in een paginavolgorde gaat, kunt u de inhoud in een andere richting schuiven, afhankelijk van of u naar een hogere of lagere pagina in de reeks gaat.
Hiervoor kunt u weergaveovergangstypen gebruiken, waarmee u een of meer typen aan een actieve weergaveovergang kunt toewijzen. Gebruik bijvoorbeeld het type forwards
bij de overgang naar een hogere pagina in een paginavolgorde en het type ' backwards
bij de overgang naar een lagere pagina. Deze typen zijn alleen actief bij het vastleggen of uitvoeren van een overgang, en elk type kan via CSS worden aangepast voor verschillende animaties.
Om typen te gebruiken in een overgang tussen weergaven van hetzelfde document, geeft u types
door aan de startViewTransition
methode. Om dit mogelijk te maken, 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],
});
Gebruik de selector :active-view-transition-type()
om op deze typen te reageren. Geef het gewenste type
door aan de selector. Zo kunt u de stijlen van meerdere weergaveovergangen van elkaar gescheiden houden, zonder dat de declaraties van de ene stijl 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
op een element alleen in of uit te schakelen 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 demo van paginering schuift de pagina-inhoud vooruit of achteruit op basis van het paginanummer waarnaar u navigeert. De typen worden bepaald door te klikken en worden doorgegeven aan document.startViewTransition
.
Als u een actieve weergave-overgang wilt targeten, ongeacht het type, kunt u in plaats daarvan de pseudo-klasseselector :active-view-transition
gebruiken.
html:active-view-transition {
…
}
Meerdere weergave-overgangsstijlen verwerken met een klassenaam op de weergave-overgangsroot
Soms heeft een overgang van het ene naar het andere type weergave een specifiek aangepaste overgang nodig. Of een 'terug'-navigatie moet anders zijn dan een 'vooruit'-navigatie.
Vóór overgangstypen werden deze gevallen afgehandeld door tijdelijk een klassenaam in te stellen op de overgangsroot. Bij het aanroepen van document.startViewTransition
is deze overgangsroot het <html>
-element, toegankelijk 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 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 media queries kan de aanwezigheid van deze klassen ook worden gebruikt om te wijzigen welke elementen een view-transition-name
krijgen.
Overgangen uitvoeren zonder andere animaties te bevriezen
Bekijk deze demo van een video waarin een overgangspositie wordt getoond:
Zag je er iets vreemds aan? Maak je geen zorgen als dat niet zo was. Hier zie je het, maar dan vertraagd:
Tijdens de overgang lijkt de video te bevriezen, waarna de afgespeelde versie van de video geleidelijk in beeld komt. Dit komt doordat ::view-transition-old(video)
een screenshot is van de oude weergave, terwijl ::view-transition-new(video)
een live -afbeelding 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 het niet veranderen.
Als je het echt wilt oplossen, laat dan ::view-transition-old(video)
niet zien; 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 gedurende de hele overgang afgespeeld.
Integratie met de Navigatie API (en andere frameworks)
Weergaveovergangen worden zo gespecificeerd dat ze kunnen worden geïntegreerd met andere frameworks of bibliotheken. Als uw single-page application (SPA) bijvoorbeeld een router gebruikt, kunt u het updatemechanisme van de router aanpassen om de inhoud bij te werken met behulp van een weergaveovergang.
In het volgende codefragment, afkomstig uit deze pagineringsdemo, wordt de onderscheppingshandler van de Navigatie-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 wanneer de gebruiker een veegbeweging maakt om te navigeren. In dat geval moet u uw eigen weergaveovergang niet activeren, omdat dit zou leiden tot een slechte of verwarrende gebruikerservaring. De gebruiker ziet dan twee overgangen – één van de browser en één van u – die achter elkaar worden weergegeven.
Daarom is het raadzaam om te voorkomen dat een weergaveovergang start wanneer de browser een eigen visuele overgang heeft voorzien. Controleer hiervoor de waarde van de eigenschap hasUAVisualTransition
van de NavigateEvent
instantie. Deze eigenschap wordt ingesteld op true
wanneer de browser een visuele overgang heeft voorzien. Deze hasUIVisualTransition
eigenschap bestaat ook in PopStateEvent
instanties.
In het vorige fragment wordt bij de controle die bepaalt of de weergaveovergang moet worden uitgevoerd, rekening gehouden met deze eigenschap. Als er geen ondersteuning is voor weergaveovergangen binnen hetzelfde document of als de browser al een eigen overgang heeft, wordt de weergaveovergang overgeslagen.
if (!document.startViewTransition || e.hasUAVisualTransition) {
setContent(newContent);
return;
}
In de volgende opname veegt de gebruiker om terug te navigeren naar de vorige pagina. De opname links bevat geen controle op de hasUAVisualTransition
vlag. De opname rechts bevat wel een controle, waardoor de handmatige weergaveovergang wordt overgeslagen omdat de browser een visuele overgang heeft.
hasUAVisualTransition
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 overgang kunnen niet alleen met CSS worden bereikt:
- De animatie start vanaf de kliklocatie.
- De animatie eindigt met een cirkel met een straal tot aan de verste hoek. Hopelijk wordt dit in de toekomst mogelijk met CSS.
Gelukkig kunt u 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 promise die wordt opgelost zodra de pseudo-elementen van de transitie succesvol zijn aangemaakt. Andere eigenschappen van dit object worden behandeld in de API-referentie .
Transities als verbetering
De View Transition API is ontworpen om een DOM-wijziging te 'verpakken' en er een transitie voor te creëren. De transitie moet echter als een verbetering worden beschouwd, dat wil zeggen dat uw app niet in een 'fout'-status mag terechtkomen als de DOM-wijziging slaagt, maar de transitie mislukt. Idealiter zou de transitie niet moeten mislukken, maar als dat wel gebeurt, mag dit de rest van de gebruikerservaring niet verstoren.
Om overgangen als een verbetering te behandelen, moet u ervoor zorgen dat u overgangsbeloftes niet op een manier gebruikt waardoor uw app in de war raakt 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()
wordt afgewezen als de overgang de status ' ready
niet kan bereiken. Dit betekent echter niet dat de weergave niet is overgeschakeld. De DOM is mogelijk wel succesvol bijgewerkt, maar er waren dubbele view-transition-name
's', waardoor de overgang is 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 deze af te wijzen als deze mislukt. switchView
wijst niet langer af als de transitie mislukt, maar lost dit op wanneer de DOM-update is voltooid en wijst dit af als deze mislukt.
Als u wilt dat switchView
wordt omgezet zodra de nieuwe weergave is 'gesetteld', dat wil zeggen wanneer een geanimeerde overgang is voltooid of naar het einde is gesprongen, vervangt u transition.updateCallbackDone
door transition.finished
.
Geen polyfill, maar...
Dit is geen eenvoudige functie om te polyfillen. Deze helperfunctie maakt het echter een stuk eenvoudiger in browsers die geen weergaveovergangen 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 || !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');
}
}
En het kan als volgt gebruikt worden:
function spaNavigate(data) {
const types = isBackNavigation ? ['back-transition'] : [];
const transition = transitionHelper({
update() {
updateTheDOMSomehow(data);
},
types,
});
// …
}
In browsers die geen weergave-overgangen ondersteunen, wordt updateDOM
nog steeds aangeroepen, maar is er geen sprake van een geanimeerde overgang.
U kunt ook een aantal classNames
opgeven die u tijdens de overgang aan <html>
kunt toevoegen. Zo kunt u de overgang eenvoudiger wijzigen, afhankelijk van het type navigatie .
Je kunt ook true
aan skipTransition
doorgeven als je geen animatie wilt, zelfs in browsers die weergaveovergangen ondersteunen. Dit is handig als je site een gebruikersvoorkeur heeft om overgangen uit te schakelen.
Werken met frameworks
Als je werkt met een bibliotheek of framework dat DOM-wijzigingen abstraheert, is het lastig om te weten wanneer de DOM-wijziging voltooid is. Hier zijn een aantal voorbeelden, met behulp van de bovenstaande helper , in verschillende frameworks.
- React — de sleutel hier is
flushSync
, dat een reeks statuswijzigingen synchroon toepast. Ja, er staat een grote waarschuwing op het gebruik van die API, maar Dan Abramov verzekert me dat het in dit geval gepast is. Zoals gebruikelijk bij React en async-code, zorg er bij het gebruik van de verschillende promises diestartViewTransition
retourneert voor dat je code met de juiste status wordt uitgevoerd. - Vue.js — de sleutel hier is
nextTick
, die wordt uitgevoerd zodra de DOM is bijgewerkt. - Svelte —erg vergelijkbaar met Vue, maar de methode om te wachten op de volgende wijziging is
tick
. - Lit — de sleutel hierbij is de
this.updateComplete
-belofte binnen componenten, die wordt vervuld zodra de DOM is bijgewerkt. - Angular — de sleutel hier is
applicationRef.tick
, die in behandeling zijnde DOM-wijzigingen verwijdert. Vanaf Angular versie 17 kun jewithViewTransitions
gebruiken, dat standaard bij@angular/router
wordt geleverd .
API-referentie
-
const viewTransition = document.startViewTransition(update)
Start een nieuwe
ViewTransition
.update
is een functie die wordt aangeroepen zodra de huidige status van het document is vastgelegd.Wanneer de belofte van
updateCallback
wordt vervuld, begint de overgang in het volgende frame. Als de belofte vanupdateCallback
wordt afgewezen, wordt de overgang afgebroken.-
const viewTransition = document.startViewTransition({ update, types })
Start een nieuwe
ViewTransition
met de opgegeven typenupdate
wordt aangeroepen zodra de huidige status van het document is vastgelegd.types
worden de actieve typen voor de overgang ingesteld bij het vastleggen of uitvoeren van de overgang. Deze is aanvankelijk leeg. ZieviewTransition.types
verderop voor meer informatie.
Instantieleden van ViewTransition
:
-
viewTransition.updateCallbackDone
Een belofte die wordt uitgevoerd als de belofte die door
updateCallback
wordt geretourneerd, wordt uitgevoerd, of wordt afgewezen als deze wordt afgewezen.De View Transition API verpakt een DOM-wijziging en creëert een overgang. Soms maakt het je echter niet uit of de overgangsanimatie slaagt of mislukt, je wilt gewoon weten of en wanneer de DOM-wijziging plaatsvindt.
updateCallbackDone
is hiervoor geschikt.-
viewTransition.ready
Een belofte die wordt nagekomen zodra de pseudo-elementen voor de overgang zijn gecreëerd en de animatie op het punt staat te beginnen.
De melding wordt afgewezen als de overgang 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 nagekomen zodra de eindtoestand volledig zichtbaar en interactief is voor de gebruiker.
Er vindt alleen een afwijzing plaats als
updateCallback
een afgewezen belofte retourneert, aangezien dit aangeeft dat de eindstatus niet is gemaakt.Anders, als een overgang niet start of wordt overgeslagen tijdens de overgang, wordt de eindtoestand toch bereikt en is
finished
vervuld.-
viewTransition.types
Een
Set
achtig object dat de typen van de actieve weergaveovergang bevat. Om de items te manipuleren, gebruikt u de instantiemethodenclear()
,add()
endelete()
.Om te reageren op een specifiek type in CSS, gebruikt u de pseudo-klasseselector
:active-view-transition-type(type)
op de overgangsroot.Typen worden automatisch opgeruimd wanneer de weergaveovergang is voltooid.
-
viewTransition.skipTransition()
Sla het animatiegedeelte van de overgang over.
Hiermee wordt
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.
width
enheight
tussen de 'voor'- en 'na'-toestanden.Overgangen
transform
tussen de 'voor' en 'na' viewport-ruimte quad.-
::view-transition-image-pair
Absoluut in staat om de groep te vullen.
Heeft
isolation: isolate
om het effect van demix-blend-mode
op de oude en nieuwe weergaven te beperken.-
::view-transition-new
::view-transition-old
Absoluut gepositioneerd linksboven op de wrapper.
Vult 100% van de groepsbreedte, maar heeft een automatische hoogte, waardoor de beeldverhouding behouden blijft in plaats van de groep te vullen.
Heeft
mix-blend-mode: plus-lighter
voor een echte cross-fade.De oude weergave gaat van
opacity: 1
naaropacity: 0
De nieuwe weergave gaat vanopacity: 0
naaropacity: 1
.
Feedback
Feedback van ontwikkelaars wordt altijd gewaardeerd. Om dit te doen, kunt u een probleem indienen bij de CSS Working Group op GitHub met suggesties en vragen. Voeg vóór uw probleem het voorvoegsel [css-view-transitions]
toe.
Mocht u een bug tegenkomen, meld dit dan als Chromium-bug .