Gepubliceerd: 17 augustus 2021, Laatst bijgewerkt: 25 september 2024
Wanneer een weergaveovergang op één document wordt uitgevoerd, wordt dit een weergaveovergang voor hetzelfde document genoemd. Dit is doorgaans het geval bij toepassingen met één pagina (SPA's) waarbij JavaScript wordt gebruikt om de DOM bij te werken. Overgangen naar weergaven van hetzelfde document worden vanaf Chrome 111 ondersteund in Chrome.
Om een weergaveovergang voor 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 de browser wordt aangeroepen, maakt deze automatisch momentopnamen van alle elementen waarvoor een CSS-eigenschap view-transition-name
is gedeclareerd.
Vervolgens voert het de doorgegeven callback uit die de DOM bijwerkt, waarna er momentopnamen worden gemaakt van de nieuwe status.
Deze snapshots worden vervolgens gerangschikt in een boom van pseudo-elementen en geanimeerd met behulp van de kracht van CSS-animaties. Paren van snapshots uit de oude en nieuwe staat gaan soepel over van hun oude positie en grootte naar hun nieuwe locatie, terwijl hun inhoud vervaagt. Als je wilt, kun je CSS gebruiken om de animaties aan te passen.
De standaardovergang: Cross-fade
De standaard weergaveovergang 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 zoals jij wilt. U kunt bijvoorbeeld elementen toevoegen of verwijderen, klassenamen wijzigen of stijlen wijzigen.
En zomaar, pagina's vervagen:
Oké, een cross-fade is niet zo indrukwekkend. Gelukkig kunnen overgangen worden aangepast, maar eerst moet je begrijpen hoe deze fundamentele cross-fade werkte.
Hoe deze transities 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. Hierbij hoort ook het maken van een momentopname.
Eenmaal voltooid, wordt de callback doorgegeven aan .startViewTransition()
aangeroepen. Dat is waar de DOM wordt gewijzigd. Vervolgens legt de API de nieuwe status van de pagina vast.
Zodra de nieuwe 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.
Pas de overgang aan
Alle pseudo-elementen van de weergaveovergang kunnen worden getarget met CSS, en aangezien de animaties worden gedefinieerd met behulp van CSS, kunt u deze wijzigen 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. 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 hier is het resultaat:
Overgang van meerdere elementen
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 (met behulp van 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 je kunt de tekst ook 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, in plaats van dezelfde grootte op beide pagina's.
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.
Animeer meerdere pseudo-elementen op dezelfde manier met view-transition-class
Browserondersteuning
Stel dat u een weergaveovergang heeft 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 zich op elke individuele kaart richt.
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? Dat zijn 20 selectors die u moet schrijven. Een nieuw element toevoegen? Dan moet je ook de selector laten groeien die de animatiestijlen toepast. Niet bepaald schaalbaar.
De view-transition-class
kan worden gebruikt in de view-transition pseudo-elementen 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. Op alle kaarten, inclusief nieuw toegevoegde kaarten, wordt dezelfde timing toegepast met één selector: html::view-transition-group(.card)
.
Debug 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 voor deze overgang is iets ingewikkelder dan het voorgaande voorbeeld, 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 vorige demo verandert de zijbalk anders, 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 de oude of nieuwe pseudo-elementen 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.
Maak optimaal gebruik van 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.
Wijzigingen in de beeldverhouding verwerken
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 bijgesneden wanneer deze overgaat van 1:1 naar 16:9.
Voor meer gedetailleerde informatie, bekijk Overgangen bekijken: Wijzigingen in de beeldverhouding verwerken
Gebruik mediaquery's om overgangen voor verschillende apparaatstatussen te wijzigen
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.
Reageer op de voorkeur 'verminderde beweging'
Gebruikers kunnen via hun besturingssysteem aangeven dat ze de voorkeur geven aan verminderde beweging, en die voorkeur wordt weergegeven in CSS .
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 voorgaande fragment zou je een subtielere animatie kunnen kiezen, maar wel één die nog steeds de relatie tussen de elementen en de gegevensstroom tot uitdrukking brengt.
Behandel meerdere weergave-overgangsstijlen met weergave-overgangstypen
Browserondersteuning
Soms moet een transitie van de ene visie naar de andere een specifiek op maat gemaakte transitie kennen. Wanneer u bijvoorbeeld naar de volgende of vorige pagina in een pagineringsreeks gaat, wilt u wellicht de inhoud in een andere richting schuiven, afhankelijk van of u naar een hogere pagina of een lagere pagina in de reeks gaat.
Hiervoor kunt u gebruik maken van weergaveovergangstypen, waarmee u aan een actieve weergaveovergang één of meerdere typen kunt toewijzen. Als u bijvoorbeeld naar een hogere pagina in een pagineringsreeks gaat, gebruikt u het forwards
type en als u naar een lagere pagina gaat, gebruikt u het backwards
type. Deze typen zijn alleen actief bij het vastleggen of uitvoeren van een overgang, en elk type kan via CSS worden aangepast om verschillende animaties te gebruiken.
Als u typen wilt gebruiken in een weergaveovergang met hetzelfde document, geeft u types
door aan de methode startViewTransition
. Om dit mogelijk te maken accepteert document.startViewTransition
ook een object: update
is de callback-functie 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 :active-view-transition-type()
selector. Geef het type
dat u wilt targeten, door in de selector. Hierdoor kunt u de stijlen van meerdere weergaveovergangen van elkaar gescheiden houden, zonder dat de declaraties van de een interfereren met de declaraties van de ander.
Omdat typen alleen van toepassing zijn bij het vastleggen of uitvoeren van de overgang, kunt u de selector gebruiken om een view-transition-name
alleen voor een element in te stellen (of uit te schakelen) voor de weergave-overgang 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 vooruit of achteruit op basis van het paginanummer waarnaar u navigeert. De typen worden bepaald door te klikken, waarna ze worden doorgegeven aan document.startViewTransition
.
Om elke actieve weergaveovergang te targeten, ongeacht het type, kunt u in plaats daarvan de :active-view-transition
pseudo-klasse selector gebruiken.
html:active-view-transition {
…
}
Verwerk meerdere weergave-overgangsstijlen met een klassenaam in de weergave-overgangswortel
Soms moet een transitie van het ene type visie naar het andere een specifiek op maat gemaakte transitie kennen. Of een 'terug'-navigatie moet anders zijn dan een 'vooruit'-navigatie.
Vóór overgangstypen was de manier om deze gevallen af te handelen het tijdelijk instellen van een klassenaam op de overgangswortel. Bij het aanroepen van document.startViewTransition
is deze transitieroot 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 transitie is voltooid, gebruikt dit voorbeeld transition.finished
, een belofte die wordt opgelost 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.
Voer overgangen uit 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.
Integratie met de Navigatie-API (en andere frameworks)
Bekijkovergangen zijn zo gespecificeerd dat ze kunnen worden geïntegreerd met andere raamwerken of bibliotheken. Als uw single-page applicatie (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 uit deze pagineringsdemo is 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 hun eigen overgang wanneer de gebruiker een veeggebaar maakt om te navigeren. In dat geval mag u niet uw eigen weergaveovergang activeren, omdat dit zou leiden tot een slechte of verwarrende gebruikerservaring. De gebruiker ziet twee overgangen (de ene door de browser en de andere door u) achter elkaar worden uitgevoerd.
Daarom wordt aanbevolen om te voorkomen dat een weergaveovergang start wanneer de browser voor zijn eigen visuele overgang heeft gezorgd. Om dit te bereiken, controleert u de waarde van de eigenschap hasUAVisualTransition
van de NavigateEvent
instantie. De eigenschap wordt ingesteld op true
wanneer de browser voor een visuele overgang heeft gezorgd. Deze eigenschap hasUIVisualTransition
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 van hetzelfde document of als de browser al een eigen overgang heeft geleverd, 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 aan de linkerkant bevat geen controle op de vlag hasUAVisualTransition
. De opname rechts bevat wel de controle, waarbij de handmatige weergaveovergang wordt overgeslagen omdat de browser voor een visuele overgang zorgde.
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 beschouwd als een verbetering, omdat uw app niet in de status 'fout' mag komen 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...
Dit is geen gemakkelijke functie om te polyfillen. Deze helperfunctie maakt het echter veel 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 worden gebruikt:
function spaNavigate(data) {
const types = isBackNavigation ? ['back-transition'] : [];
const transition = transitionHelper({
update() {
updateTheDOMSomehow(data);
},
types,
});
// …
}
In browsers die weergaveovergangen 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
als u geen animatie wilt, zelfs in browsers die weergaveovergangen 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(update)
Start een nieuwe
ViewTransition
.update
is een functie die 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.-
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
stelt de actieve typen in voor de overgang bij het vastleggen of uitvoeren van de overgang. Het is aanvankelijk leeg. ZieviewTransition.types
verderop voor meer informatie.
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 je echter niets uit of de transitie-animatie slaagt of mislukt, maar wil je gewoon weten of en wanneer de DOM-verandering 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 tijdens de transitie wordt overgeslagen, wordt de eindtoestand nog steeds bereikt, dus
finished
is vervuld.-
viewTransition.types
Een
Set
-achtig object dat de typen van de actieve weergaveovergang bevat. Om de gegevens te manipuleren, gebruikt u de instantiemethodenclear()
,add()
endelete()
.Om op een specifiek type in CSS te reageren, gebruikt u de
:active-view-transition-type(type)
pseudo-klasse selector op de transitieroot.Typen worden automatisch opgeschoond wanneer de weergaveovergang is voltooid.
-
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 demix-blend-mode
op de oude en nieuwe weergaven te beperken.-
::view-transition-new
::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 dat de groep gevuld wordt.
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 wordt altijd op prijs gesteld. Om dit te doen, dient u een probleem in bij de CSS Working Group op GitHub met suggesties en vragen. Geef uw probleem een voorvoegsel met [css-view-transitions]
.
Mocht je een bug tegenkomen, dien dan een Chromium-bug in .