Weergaveovergangen van hetzelfde document voor toepassingen met één pagina

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:

De standaard cross-fade. Minimale demo . Bron .

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:

Lange cross-fade. Minimale demo . Bron .

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:

Gedeelde asovergang. Minimale demo . Bron .

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:

Gedeelde asovergang met vaste header. Minimale demo . Bron .

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:

Glijdende koptekst. Minimale demo . Bron .

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

  • Chroom: 125.
  • Rand: 125.
  • Firefox: niet ondersteund.
  • Safari Technology Preview: ondersteund.

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) .

Opname van de Cards-demo . Met behulp van view-transition-class past het dezelfde animation-timing-function toe op alle kaarten behalve de toegevoegde of verwijderde.

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 .

Foutopsporing in weergaveovergangen met Chrome DevTools.

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:

Het ene element gaat over in het andere. Minimale demo . Bron .

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:

Zijbalk openen en verlaten. Minimale demo . Bron .

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 miniatuur gaat over naar een grotere afbeelding. Probeer de demosite .

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?

Het ene element gaat over naar het andere, met een verandering in de beeldverhouding. Minimale demo . Bron .

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:

Het ene element gaat over in het andere. Minimale demo . Bron .

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

  • Chroom: 125.
  • Rand: 125.
  • Firefox: niet ondersteund.
  • Safari: 18.

Soms moet een transitie van de ene visie naar de andere een specifiek op maat gemaakte transitie kennen. Als u bijvoorbeeld naar de volgende of vorige pagina in een pagineringsreeks gaat, wilt u de inhoud mogelijk in een andere richting schuiven, afhankelijk van of u naar een hogere of een lagere pagina in de reeks gaat.

Opname van de Paginatie-demo . Er worden verschillende overgangen gebruikt, afhankelijk van naar welke pagina u 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.

Verschillende overgangen bij het ‘teruggaan’. Minimale demo . Bron .

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:

Video-overgang. Minimale demo . Bron .

Heb je er iets verkeerds aan gezien? Maak je geen zorgen als je dat niet hebt gedaan. Hier wordt het vertraagd:

Video-overgang, langzamer. Minimale demo . Bron .

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!

Video-overgang, langzamer. Minimale demo . Bron .

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.

Vergelijking van dezelfde site zonder (links) en breedte (rechts) een controle op hasUAVisualTransition

Animeren met JavaScript

Tot nu toe zijn alle overgangen gedefinieerd met behulp van CSS, maar soms is CSS niet voldoende:

Cirkel overgang. Minimale demo . Bron .

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 zorgt dat uw app een foutmelding krijgt als de overgang mislukt.

Niet doen
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:

Doen
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 door startViewTransition 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 u withViewTransitions 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 door updateCallback geretourneerde belofte wordt afgewezen, wordt de transitie afgebroken.

const viewTransition = document.startViewTransition({ update, types })

Start een nieuwe ViewTransition met de opgegeven typen

update 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. Zie viewTransition.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 als updateCallback 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.types

Een Set -achtig object dat de typen van de actieve weergaveovergang bevat. Om de gegevens te manipuleren, gebruikt u de instantiemethoden clear() , add() en delete() .

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 en height 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 de mix-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 naar opacity: 0 . De nieuwe weergave gaat over van opacity: 0 naar opacity: 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 .

,

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:

De standaard cross-fade. Minimale demo . Bron .

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 weergavestransitie pseudo-elementen kunnen worden gericht met CSS, en omdat de animaties worden gedefinieerd met behulp van CSS, kunt u ze 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 fade nu erg traag:

Lange cross-fade. Minimale demo . Bron .

Oké, dat is nog steeds niet indrukwekkend. In plaats daarvan implementeert de volgende code de gedeelde asovergang van materiaalontwerp :

@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:

Gedeelde asovergang. Minimale demo . Bron .

Overgang meerdere elementen

In de vorige demo is de hele pagina betrokken bij de overgang van de gedeelde as. Dat werkt het grootste deel van de pagina, maar het lijkt niet helemaal goed voor de kop, omdat het eruit glijdt om weer naar binnen te glijden.

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 wilt (behalve none , wat betekent dat er geen overgangsnaam is). Het wordt gebruikt om het element over de overgang op unieke wijze te identificeren.

En het resultaat daarvan:

Gedeelde asovergang met vaste header. Minimale demo . Bron .

Nu blijft de header op zijn plaats en cross-fades.

Die CSS-verklaring 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. Een voor de header en een andere voor de rest. Deze kunnen onafhankelijk van CSS worden gericht en verschillende overgangen krijgen. Hoewel, in dit geval, main-header de standaardovergang bleef, die een cross-fade is.

Nou, oké, de standaardovergang is niet alleen een kruisvervaging, de ::view-transition-group overgaat ook:

  • Positie en transformeren (met behulp van een transform )
  • Breedte
  • Hoogte

Dat is tot nu toe niet uitgesproken, omdat de koptekst dezelfde grootte heeft en beide kanten van de DOM -verandering positioneren. Maar u kunt de tekst ook in de header extraheren:

.main-header-text {
  view-transition-name: main-header-text;
  width: fit-content;
}

fit-content wordt gebruikt, dus het element is de grootte van de tekst, in plaats van zich tot de resterende breedte te strekken. Zonder dit vermindert de achterste pijl de grootte van het kopteksttekstelement, 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:

Schuifkoptekst. Minimale demo . Bron .

Nu doet de koptekst een beetje bevredigende schuif om ruimte te maken voor de back -knop.


Animate meerdere pseudo-elementen op dezelfde manier met view-transition-class

Browserondersteuning

  • Chrome: 125.
  • Edge: 125.
  • Firefox: niet ondersteund.
  • Preview van Safari Technology: ondersteund.

Stel dat u een weergavestransitie hebt met een stel kaarten, maar ook een titel op de pagina. Om alle kaarten te animeren, behalve de titel, moet u een selector schrijven die zich op elke afzonderlijke 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 weergave 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 kaartenvoorbeeld maakt gebruik van het vorige CSS -fragment. Alle kaarten-inclusief nieuw toegevoegde kaarten-krijgen dezelfde timing toegepast met één selector: html::view-transition-group(.card) .

Opname van de kaarten Demo . Met behulp van view-transition-class past het dezelfde animation-timing-function toe op alle kaarten behalve de toegevoegde of verwijderde.

Debug -overgangen

Omdat weergavestransities bovenop CSS -animaties zijn gebouwd, is het animatiesaneel in Chrome Devtools geweldig voor foutopsporingsovergangen.

Met behulp van het animatiespaneel kunt u de volgende animatie pauzeren en vervolgens door de animatie heen en weer schrobben. Tijdens dit zijn de overgangspseudo-elementen te vinden in het elementenpaneel .

Debugging view overgangen met Chrome Devtools.

Overgangselementen hoeven niet hetzelfde DOM -element te zijn

Tot nu toe hebben we view-transition-name gebruikt om afzonderlijke overgangselementen voor de header en de tekst in de koptekst te maken. Dit zijn conceptueel hetzelfde element voor en na de DOM -verandering, maar u kunt overgangen maken waar dat niet het geval is.

De hoofdvideo-inbedding kan bijvoorbeeld een view-transition-name krijgen:

.full-embed {
  view-transition-name: full-embed;
}

Wanneer 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:

Het ene element dat overgaat naar het andere. Minimale demo . Bron .

De miniatuur gaat nu over in de hoofdafbeelding. Hoewel ze conceptueel (en letterlijk) verschillende elementen zijn, behandelt de overgangs-API ze als hetzelfde omdat ze dezelfde view-transition-name deelden.

De echte code voor deze overgang is iets gecompliceerder dan het voorgaande voorbeeld, omdat deze ook de overgang teruggaat naar de miniatuurpagina. Zie de bron voor de volledige implementatie.


Aangepaste invoer- en exit -overgangen

Kijk naar dit voorbeeld:

Zijbalk invoeren en verlaten. Minimale demo . Bron .

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 zo 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 de Pseudo-element de ::view-transition-old(sidebar) er niet zijn. Aangezien er geen 'oude' afbeelding is voor de zijbalk, heeft de beeldpaar alleen een ::view-transition-new(sidebar) . Evenzo, als de zijbalk zich alleen op de oude pagina bevindt, heeft de beeldpaar alleen een ::view-transition-old(sidebar) .

In de vorige demo, de zijbalkovergangen anders, afhankelijk van of het in beide staten binnenkomt, weggaat of aanwezig is. Het komt binnen door van rechts te glijden en te vervagen, het verlaat door naar rechts te glijden en te vervagen, en het blijft op zijn plaats wanneer het in beide staten aanwezig is.

Om specifieke invoer- en exit-overgangen te maken, kunt u de :only-child pseudo-klasse gebruiken om zich te richten op de oude of nieuwe pseudo-elementen wanneer dit het enige kind in de beeldpaar 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 aanwezig is in beide staten, omdat de standaard perfect is.

Async DOM -updates en wachten op inhoud

De callback die is doorgegeven aan .startViewTransition() kan een belofte retourneren, waarmee ASYNC DOM -updates mogelijk zijn en wachten tot belangrijke inhoud klaar is.

document.startViewTransition(async () => {
  await something;
  await updateTheDOMSomehow();
  await somethingElse;
});

De overgang zal pas worden gestart totdat de belofte vervult. Gedurende deze tijd is de pagina bevroren, dus hier moeten vertragingen tot een minimum worden beperkt. In het bijzonder moeten netwerkhakken worden gedaan voordat u .startViewTransition() oproept, terwijl de pagina nog steeds volledig interactief is, in plaats van ze te doen als onderdeel van de callback .startViewTransition() .

Als u besluit te wachten tot afbeeldingen of lettertypen klaar zijn, moet u een agressieve time -out gebruiken:

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 die u al heeft te gebruiken.


Haal het meeste uit de inhoud die u al hebt

In het geval waarin de miniatuur overgaat naar een groter beeld:

De miniatuur die overstaat naar een groter beeld. Probeer de demo -site .

De standaardovergang is om te betalen, wat betekent dat de miniatuur kan worden gekruist met een nog niet-geladen volledige afbeelding.

Een manier om dit aan te pakken is om te wachten tot de volledige afbeelding wordt geladen voordat de overgang wordt gestart. Idealiter zou dit worden gedaan voordat u .startViewTransition() oproept, zodat de pagina interactief blijft en kan worden aangetoond dat een spinner aan de gebruiker aangeven dat dingen laden. 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, hij zit 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 de volledige afbeelding in zijn eigen tijd kan laden.

Dit zou niet werken als de nieuwe weergave transparantie zou zijn, maar in dit geval weten we dat het niet, zodat we deze optimalisatie kunnen maken.

Behandel veranderingen in de beeldverhouding

Handig zijn alle overgangen tot nu toe naar elementen geweest met dezelfde beeldverhouding, maar dat zal niet altijd het geval zijn. Wat als de miniatuur 1: 1 is en de hoofdafbeelding 16: 9 is?

Het ene element dat overgaat naar het andere, met een verandering van beeldverhouding. Minimale demo . Bron .

In de standaardovergang animeert de groep van de vóór de grootte naar de aftergrootte. De oude en nieuwe weergaven zijn 100% breedte van de groep en autohoogte, wat betekent dat ze hun beeldverhouding behouden, ongeacht de grootte van de groep.

Dit is een goede standaard, maar het is in dit geval niet gewenst. 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 uitbreidt, maar de volledige afbeelding 'un-crops' terwijl deze overgaat van 1: 1 tot 16: 9.

Voor meer gedetailleerde informatie, bekijk weergavestransities: wijzigingen op de beeldverhouding afhandelen


Gebruik media -query's om overgangen voor verschillende apparaatstatussen te wijzigen

Misschien wilt u verschillende overgangen gebruiken op mobiel versus desktop, zoals dit voorbeeld dat een volledige dia van de zijkant op mobiel uitvoert, maar een subtielere dia op desktop:

Het ene element dat overgaat naar het andere. Minimale demo . Bron .

Dit kan worden bereikt met behulp van reguliere media -vragen:

/* 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;
  }
}

Misschien wilt u ook wijzigen welke elementen u een view-transition-name toewijst, afhankelijk van bijpassende media-vragen.


Reageren op de voorkeur van de 'verminderde beweging'

Gebruikers kunnen aangeven dat ze de voorkeur geven aan verminderde beweging via hun besturingssysteem en dat de voorkeur wordt blootgesteld in CSS .

U zou kunnen 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 '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 een die nog steeds de relatie tussen elementen en de gegevensstroom uitdrukt.


Behandel meerdere weergavestijlen met weergavestypes

Browserondersteuning

  • Chrome: 125.
  • Edge: 125.
  • Firefox: niet ondersteund.
  • Safari: 18.

Soms zou een overgang van de ene bepaalde kijk naar een andere een specifiek op maat gemaakte overgang moeten hebben. Wanneer u bijvoorbeeld naar de volgende of naar de vorige pagina gaat in een paginatievolgorde, wilt u misschien de inhoud in een andere richting schuiven, afhankelijk van of u naar een hogere pagina of een lagere pagina van de reeks gaat.

Opname van de paginatiedemo . Het gebruikt verschillende overgangen, afhankelijk van welke pagina u gaat.

Hiervoor kunt u weergaveversities gebruiken, waarmee u een of meer typen kunt toewijzen aan een actieve weergavestransitie. Gebruik bijvoorbeeld wanneer overgang naar een hogere pagina in een paginatievolgorde het forwards type en naar een lagere pagina gaan, gebruik 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.

Om typen te gebruiken in een overgang van dezelfde documentweergave, geeft u types door in de methode startViewTransition . Om dit toe te staan, 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],
});

Gebruik de selector :active-view-transition-type() om op deze typen te reageren op deze typen. Geef het type door dat u in de selector wilt targeten. Hierdoor kunt u de stijlen van meerdere weergavestransities van elkaar houden, zonder de verklaringen van degene die de verklaringen van de ander verstoort.

Omdat typen alleen van toepassing zijn bij het vastleggen of uitvoeren van de overgang, kunt u de selector gebruiken om te instellen-of niet-set-een view-transition-name op een element alleen voor de weergaveovergang met dat type.

/* Determine what gets captured when the type is forwards or backwards */
html:active-view-transition-type(forwards, backwards) {
  :root {
    view-transition-name: none;
  }
  article {
    view-transition-name: content;
  }
  .pagination {
    view-transition-name: pagination;
  }
}

/* Animation styles for forwards type only */
html:active-view-transition-type(forwards) {
  &::view-transition-old(content) {
    animation-name: slide-out-to-left;
  }
  &::view-transition-new(content) {
    animation-name: slide-in-from-right;
  }
}

/* Animation styles for backwards type only */
html:active-view-transition-type(backwards) {
  &::view-transition-old(content) {
    animation-name: slide-out-to-right;
  }
  &::view-transition-new(content) {
    animation-name: slide-in-from-left;
  }
}

/* Animation styles for reload type only (using the default root snapshot) */
html:active-view-transition-type(reload) {
  &::view-transition-old(root) {
    animation-name: fade-out, scale-down;
  }
  &::view-transition-new(root) {
    animation-delay: 0.25s;
    animation-name: fade-in, scale-up;
  }
}

In de volgende paginatiedemo glijden de pagina -inhoud naar voren of achteruit op basis van het paginanummer waarnaar u navigeert. De typen worden bepaald op de klik waarop ze in document.startViewTransition worden doorgegeven. StartViewTransition.

Om elke actieve weergavetransitie te richten, ongeacht het type, kunt u in plaats daarvan de Pseudo-klasse selector :active-view-transition gebruiken.

html:active-view-transition {
    …
}

Behandel meerdere weergavestijlen met een klassennaam op de weergavestransitiewortel

Soms zou een overgang van het ene soort zicht naar het andere een specifiek op maat gemaakte overgang moeten hebben. Of een 'terug' navigatie moet anders zijn dan een 'voorwaartse' navigatie.

Verschillende overgangen bij het 'terug'. Minimale demo . Bron .

Voordat overgangstypen de manier om deze gevallen te behandelen, was het tijdelijk instellen van een klassennaam op de overgangswortel. Bij het aanroepen document.startViewTransition is deze overgangsroot het element <html> , toegankelijk met 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 na de afwerking van de overgang, gebruikt dit voorbeeld transition.finished . Vaste belofte, een belofte die oplost zodra de overgang zijn eindtoestand heeft bereikt. Andere eigenschappen van dit object worden behandeld in de API -referentie .

Nu kunt u die klassennaam 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-vragen kan de aanwezigheid van deze klassen ook worden gebruikt om te veranderen welke elementen een view-transition-name krijgen.


Voer overgangen uit zonder andere animaties te bevriezen

Bekijk deze demo van een video -overgangspositie:

Videovergang. Minimale demo . Bron .

Heb je er iets mis mee gezien? Maak je geen zorgen als je dat niet deed. Hier wordt het vertraagd:

Videovergang, langzamer. Minimale demo . Bron .

Tijdens de ::view-transition-old(video) ::view-transition-new(video) de video te bevriezen, dan vervaagt de speelversie van de video. ::view-transition-new(video) is een live afbeelding van de nieuwe weergave.

Je kunt dit oplossen, maar vraag jezelf eerst af of het de moeite waard is om te repareren. Als je het 'probleem' niet zou zien wanneer de overgang met zijn normale snelheid speelde, zou ik het niet de moeite nemen om het te veranderen.

Als je het echt wilt repareren, toon dan niet de ::view-transition-old(video) ; Schakel rechtstreeks naar de ::view-transition-new(video) . U kunt dit doen door de standaardstijlen en animaties te overschrijden:

::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!

Videovergang, langzamer. Minimale demo . Bron .

Nu speelt de video tijdens de overgang.


Integratie met de navigatie -API (en andere frameworks)

Bekijkovergangen worden zodanig gespecificeerd dat ze kunnen worden geïntegreerd met andere frameworks of bibliotheken. Als uw applicatie voor één pagina bijvoorbeeld (SPA) een router gebruikt, kunt u het updatemechanisme van de router aanpassen om de inhoud bij te werken met behulp van een weergavestransitie.

In het volgende codefragment van deze paginatiedemo wordt de interceptie -handler van de navigatie -API aangepast om document.startViewTransition 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 swipe -gebaar uitvoert om te navigeren. In dat geval moet u uw eigen overgang niet activeren, omdat dit zou leiden tot een slechte of verwarrende gebruikerservaring. De gebruiker zou twee overgangen zien - de ene die door de browser en de andere door u wordt geleverd - achter elkaar aflopen.

Daarom wordt aanbevolen om te voorkomen dat een weergave -overgang begint wanneer de browser zijn eigen visuele overgang heeft verstrekt. Controleer om dit te bereiken de waarde van de eigenschap hasUAVisualTransition van de NavigateEvent -instantie. De eigenschap is ingesteld op true wanneer de browser een visuele overgang heeft verstrekt. Dit onroerend goed hasUIVisualTransition bestaat ook op instanties PopStateEvent .

In het vorige fragment houdt de cheque die bepaalt of de weergave -overgang moet worden uitgevoerd, rekening met deze eigenschap. Wanneer er geen ondersteuning is voor overgangen van hetzelfde document of wanneer de browser al zijn eigen overgang heeft verstrekt, wordt de weergaveversie 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 cheque voor de vlag hasUAVisualTransition . De opname aan de rechterkant omvat de cheque, waardoor de overgang van de handmatige weergave wordt overgeslagen omdat de browser een visuele overgang bood.

Vergelijking van dezelfde site zonder (links) en breedte (rechts) een cheque op hasUAVisualTransition

Animeren met JavaScript

Tot nu toe zijn alle overgangen gedefinieerd met behulp van CSS, maar soms is CSS niet genoeg:

Cirkelovergang. Minimale demo . Bron .

Een paar delen van deze overgang kunnen niet alleen met CSS worden bereikt:

  • De animatie begint bij de kliklocatie.
  • De animatie eindigt met de cirkel met een straal naar de verste hoek. Hoewel, hopelijk zal dit in de toekomst met CSS mogelijk zijn.

Gelukkig kunt u overgangen maken met behulp van de Web Animation API !

let lastClick;
addEventListener('click', event => (lastClick = event));

function spaNavigate(data) {
  // Fallback for browsers that don't support this API:
  if (!document.startViewTransition) {
    updateTheDOMSomehow(data);
    return;
  }

  // Get the click position, or fallback to the middle of the screen
  const x = lastClick?.clientX ?? innerWidth / 2;
  const y = lastClick?.clientY ?? innerHeight / 2;
  // Get the distance to the furthest corner
  const endRadius = Math.hypot(
    Math.max(x, innerWidth - x),
    Math.max(y, innerHeight - y)
  );

  // With a transition:
  const transition = document.startViewTransition(() => {
    updateTheDOMSomehow(data);
  });

  // Wait for the pseudo-elements to be created:
  transition.ready.then(() => {
    // Animate the root's new view
    document.documentElement.animate(
      {
        clipPath: [
          `circle(0 at ${x}px ${y}px)`,
          `circle(${endRadius}px at ${x}px ${y}px)`,
        ],
      },
      {
        duration: 500,
        easing: 'ease-in',
        // Specify which pseudo-element to animate
        pseudoElement: '::view-transition-new(root)',
      }
    );
  });
}

Dit voorbeeld maakt gebruik van transition.ready , een belofte die oplost zodra de overgangspseudo-elementen met succes zijn gemaakt. Andere eigenschappen van dit object worden behandeld in de API -referentie .


Overgangen als een verbetering

De weergavestransitie API is ontworpen om een ​​DOM -verandering te 'inpakken' en er een overgang voor te maken. De overgang moet echter worden behandeld als een verbetering, zoals in, uw app mag geen 'fout' -status invoeren als de DOM -wijziging slaagt, maar de overgang mislukt. Idealiter zou de overgang niet moeten falen, maar als dit het geval is, zou deze de rest van de gebruikerservaring niet moeten breken.

Om overgangen als een verbetering te behandelen, moet u ervoor zorgen dat u geen overgangsbeloften gebruikt op een manier die ervoor zou zorgen dat uw app gooit als de overgang mislukt.

Niet doen
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 overgang geen ready status kan bereiken, maar dat betekent niet dat de weergave niet kan worden overgeschakeld. De DOM is misschien met succes bijgewerkt, maar er waren dubbele view-transition-name , dus de overgang werd overgeslagen.

In plaats van:

Doen
async function switchView(data) {
  // Fallback for browsers that don't support this API:
  if (!document.startViewTransition) {
    await updateTheDOM(data);
    return;
  }

  const transition = document.startViewTransition(async () => {
    await updateTheDOM(data);
  });

  animateFromMiddle(transition);

  await transition.updateCallbackDone;
}

async function animateFromMiddle(transition) {
  try {
    await transition.ready;

    document.documentElement.animate(
      {
        clipPath: [`inset(50%)`, `inset(0)`],
      },
      {
        duration: 500,
        easing: 'ease-in',
        pseudoElement: '::view-transition-new(root)',
      }
    );
  } catch (err) {
    // You might want to log this error, but it shouldn't break the app
  }
}

Dit voorbeeld maakt gebruik van transition.updateCallbackDone om te wachten op de DOM -update en om te verwerpen of deze mislukt. switchView weigert niet langer als de overgang mislukt, deze lost op wanneer de DOM -update is voltooid en weigert of deze mislukt.

Als u wilt dat switchView oplossen wanneer de nieuwe weergave is 'geregeld', zoals in, is elke geanimeerde overgang voltooid of overgeslagen, vervangt transition.updateCallbackDone door transition.finished .


Geen polyfill, maar ...

Dit is geen gemakkelijke functie om polyfill te maken. Deze helperfunctie maakt het echter veel eenvoudiger in browsers die geen overgangen van weergave 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 zo worden gebruikt:

function spaNavigate(data) {
  const types = isBackNavigation ? ['back-transition'] : [];

  const transition = transitionHelper({
    update() {
      updateTheDOMSomehow(data);
    },
    types,
  });

  // …
}

In browsers die geen weergavestransities ondersteunen, zal updateDOM nog steeds worden opgeroepen, maar er zal geen geanimeerde overgang zijn.

U kunt ook enkele classNames verstrekken om toe te voegen aan <html> tijdens de overgang, waardoor het gemakkelijker wordt om de overgang te wijzigen, afhankelijk van het type navigatie .

Je kunt ook true passeren aan skipTransition als je geen animatie wilt, zelfs in browsers die overgangen van weergave ondersteunen. Dit is handig als uw site een gebruikersvoorkeur heeft om overgangen uit te schakelen.


Werken met frameworks

Als u werkt met een bibliotheek of framework dat DOM weggooit, is het lastige deel te weten wanneer de DOM -verandering is voltooid. Hier is een aantal voorbeelden, die de helper hierboven gebruiken, in verschillende frameworks.

  • React —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 me dat het in dit geval geschikt is. Zoals gebruikelijk met React- en Async -code, zorg er bij het gebruik van de verschillende beloften die worden geretourneerd door startViewTransition , voor dat uw code met de juiste status wordt uitgevoerd.
  • Vue.js —De sleutel hier is nextTick , die vervult zodra de DOM is bijgewerkt.
  • SVELTE —NEER vergelijkbaar met Vue, maar de methode om te wachten op de volgende verandering is tick .
  • Lit —De sleutel hier is de this.updateComplete belofte binnen componenten, die vervult zodra de DOM is bijgewerkt.
  • Angular —De sleutel hier is applicationRef.tick , die spoelt in afwachting van DOM -veranderingen. Vanaf Angular -versie 17 kunt u withViewTransitions gebruiken die wordt geleverd met @angular/router .

API -referentie

const viewTransition = document.startViewTransition(update)

Start een nieuwe ViewTransition .

update is een functie die wordt opgeroepen zodra de huidige status van het document is vastgelegd.

Dan, wanneer de belofte die door updateCallback wordt geretourneerd, vervult, begint de overgang in het volgende frame. Als de belofte wordt geretourneerd door updateCallback , wordt de overgang verlaten.

const viewTransition = document.startViewTransition({ update, types })

Start een nieuwe ViewTransition met de opgegeven typen

update wordt opgeroepen 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. Zie viewTransition.types verder voor meer informatie.

Exemplaar leden van ViewTransition :

viewTransition.updateCallbackDone

Een belofte die voldoet wanneer de belofte wordt geretourneerd door updateCallback vervult of verwerpt wanneer deze weigert.

De weergavestransitie API wikkelt een DOM -verandering en creëert een overgang. Soms geeft u echter niet om het succes of falen van de overgangsanimatie, u wilt gewoon weten of en wanneer de DOM -verandering plaatsvindt. updateCallbackDone is voor die use-case.

viewTransition.ready

Een belofte die vervult zodra de pseudo-elementen voor de overgang zijn gemaakt, en de animatie staat op het punt te starten.

Het verwerpt of de overgang niet kan beginnen. Dit kan te wijten zijn aan een verkeerde configuratie, zoals dubbele view-transition-name S, of als updateCallback een afgewezen belofte retourneert.

Dit is handig voor het animeren van de overgangspseudo-elementen met JavaScript .

viewTransition.finished

Een belofte die vervult zodra de eindstatus volledig zichtbaar en interactief is voor de gebruiker.

Het verwerpt alleen als updateCallback een afgewezen belofte retourneert, omdat dit aangeeft dat de eindstatus niet is gemaakt.

Anders, als een overgang niet begint of wordt overgeslagen tijdens de overgang, wordt de eindstatus nog steeds bereikt, dus finished voldaan.

viewTransition.types

Een Set -achtig object dat de typen van de Active View -overgang bevat. Gebruik de instantiemethoden clear() , add() en delete() om de vermeldingen te manipuleren.

Om te reageren op een specifiek type in CSS, gebruikt u de :active-view-transition-type(type) Pseudo-klasse selector op de overgangswortel.

Typen worden automatisch opgeruimd wanneer de overgang van het weergave eindigt.

viewTransition.skipTransition()

Sla het animatiegedeelte van de overgang over.

Dit zal het bellen van updateCallback niet overslaan, omdat de DOM -wijziging gescheiden is van de overgang.


Standaardstijl en overgangsreferentie

::view-transition
De wortelpseudo-element die het viewport vult en elke ::view-transition-group bevat.
::view-transition-group

Absoluut gepositioneerd.

Overgangen width en height tussen de 'vóór' en 'na' staten.

Overgangen transform tussen de 'vóór' en 'After' Viewport-Space Quad.

::view-transition-image-pair

Absoluut gepositioneerd om de groep te vullen.

Heeft isolation: isolate om het effect van de mix-blend-mode op de oude en nieuwe weergaven te beperken.

::view-transition-new ::view-transition-old

Absoluut geplaatst naar linksboven van de wikkel.

Vult 100% van de groepsbreedte, maar heeft een autohoogte, dus het zal zijn beeldverhouding handhaven in plaats van de groep te vullen.

Heeft mix-blend-mode: plus-lighter om een ​​echte cross-fade mogelijk te maken.

De oude weergave overgaat van opacity: 1 naar opacity: 0 . De nieuwe weergave overgaat van opacity: 0 naar opacity: 1 .


Feedback

Feedback van ontwikkelaars wordt altijd op prijs gesteld. Om dit te doen, dient u een probleem in met de CSS -werkgroep op GitHub met suggesties en vragen. Voorvoegsel uw probleem met [css-view-transitions] .

Mocht u een bug tegenkomen en dien in plaats daarvan een chroombug in .