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, spreken we van een weergaveovergang binnen hetzelfde document . Dit is doorgaans het geval bij single-page applicaties (SPA's) waar JavaScript wordt gebruikt om de DOM bij te werken. Weergaveovergangen binnen hetzelfde document worden in Chrome ondersteund vanaf versie 111.

Om een ​​overgang tussen weergaven van hetzelfde document te activeren, roept u document.startViewTransition aan:

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

  // With a View Transition:
  document.startViewTransition(() => updateTheDOMSomehow());
}

Wanneer deze functie wordt aangeroepen, maakt de browser automatisch momentopnamen van alle elementen waarop de CSS-eigenschap ` view-transition-name is gedeclareerd.

Vervolgens voert het de meegegeven callback uit die de DOM bijwerkt, waarna het momentopnamen maakt van de nieuwe toestand.

Deze momentopnamen worden vervolgens gerangschikt in een boomstructuur van pseudo-elementen en geanimeerd met behulp van CSS-animaties. Paren van momentopnamen uit de oude en nieuwe staat gaan vloeiend over van hun oude positie en grootte naar hun nieuwe locatie, terwijl de inhoud in elkaar overvloeit. Desgewenst kunt u de animaties aanpassen met CSS.


De standaardovergang: Crossfade

De standaard overgang tussen schermen is een crossfade, wat een prettige introductie tot de API oplevert:

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

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

Bij updateTheDOMSomehow wordt de DOM op een of andere manier naar een nieuwe staat gewijzigd. Dit kan op verschillende manieren. Je kunt bijvoorbeeld elementen toevoegen of verwijderen, klassenamen wijzigen of stijlen aanpassen.

En plotseling vloeien de pagina's in elkaar over:

De standaard crossfade. Minimale demo . Bron .

Oké, een crossfade is niet zo indrukwekkend. Gelukkig kunnen overgangen worden aangepast, maar eerst moet je begrijpen hoe deze basis crossfade werkt.


Hoe deze overgangen werken

Laten we het vorige codevoorbeeld bijwerken.

document.startViewTransition(() => updateTheDOMSomehow(data));

Wanneer .startViewTransition() wordt aangeroepen, legt de API de huidige status van de pagina vast. Dit omvat ook het maken van een momentopname.

Zodra dit is voltooid, wordt de callback die is doorgegeven aan .startViewTransition() aangeroepen. Daar wordt de DOM gewijzigd. Vervolgens legt de API de nieuwe status van de pagina vast.

Zodra de nieuwe status is vastgelegd, construeert de API een pseudo-elementenboom zoals deze:

::view-transition
└─ ::view-transition-group(root)
   └─ ::view-transition-image-pair(root)
      ├─ ::view-transition-old(root)
      └─ ::view-transition-new(root)

De ::view-transition wordt als een overlay over de rest van de pagina geplaatst. Dit is handig als je een achtergrondkleur voor de overgang wilt instellen.

::view-transition-old(root) is een screenshot van de oude weergave, en ::view-transition-new(root) is een live weergave van de nieuwe weergave. Beide worden weergegeven als CSS 'vervangende inhoud' (zoals een <img> ).

De oude weergave verandert geleidelijk van opacity: 1 naar opacity: 0 , terwijl de nieuwe weergave geleidelijk verandert van opacity: 0 naar opacity: 1 , waardoor een crossfade ontstaat.

Alle animaties worden uitgevoerd met CSS-animaties, waardoor ze met CSS kunnen worden aangepast.

De overgang aanpassen

Alle overgangs-pseudo-elementen kunnen met CSS worden aangestuurd, en aangezien de animaties met CSS zijn gedefinieerd, kunt u ze aanpassen met behulp van bestaande CSS-animatie-eigenschappen. Bijvoorbeeld:

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 5s;
}

Door die ene verandering verloopt het vervagen nu echt heel langzaam:

Lange crossfade. 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 dit is het resultaat:

Overgang met gedeelde as. Minimale demo . Bron .

Meerdere elementen overzetten

In de vorige demo was de hele pagina betrokken bij de overgang via de gedeelde as. Dat werkt voor het grootste deel van de pagina, maar het lijkt niet helemaal geschikt voor de koptekst, omdat die naar buiten schuift om vervolgens weer naar binnen te schuiven.

Om dit te voorkomen, kunt u de header loskoppelen van de rest van de pagina, zodat deze afzonderlijk kan worden geanimeerd. Dit doet u door een view-transition-name toe te wijzen aan het element.

.main-header {
  view-transition-name: main-header;
}

De waarde van view-transition-name kan willekeurig zijn (behalve none , wat betekent dat er geen overgangsnaam is). Deze waarde wordt gebruikt om het element uniek te identificeren tijdens de overgang.

En het resultaat daarvan:

Gedeelde asovergang met vaste koptekst. Minimale demo . Broncode .

Nu blijft de koptekst op zijn plaats en vervaagt deze.

Die CSS-declaratie zorgde ervoor dat de pseudo-elementstructuur veranderde:

::view-transition
├─ ::view-transition-group(root)
│  └─ ::view-transition-image-pair(root)
│     ├─ ::view-transition-old(root)
│     └─ ::view-transition-new(root)
└─ ::view-transition-group(main-header)
   └─ ::view-transition-image-pair(main-header)
      ├─ ::view-transition-old(main-header)
      └─ ::view-transition-new(main-header)

Er zijn nu twee overgangsgroepen. Eén voor de header en één voor de rest. Deze kunnen onafhankelijk van elkaar met CSS worden aangestuurd en van verschillende overgangen worden voorzien. In dit geval is voor de main-header de standaardovergang, een crossfade, behouden.

Oké, de standaardovergang is dus niet alleen een crossfade, de ::view-transition-group voert ook overgangen uit:

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

Dat was tot nu toe geen probleem, omdat de header aan beide zijden van de DOM dezelfde grootte en positie behoudt. Maar je kunt de tekst in de header ook extraheren:

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

fit-content wordt gebruikt zodat het element de grootte van de tekst heeft, in plaats van zich uit te rekken tot de resterende breedte. Zonder dit zou de terugpijl de grootte van het koptekstelement verkleinen, in plaats van dat het op beide pagina's dezelfde grootte behoudt.

We hebben nu dus drie onderdelen om mee te spelen:

::view-transition
├─ ::view-transition-group(root)
│  └─ …
├─ ::view-transition-group(main-header)
│  └─ …
└─ ::view-transition-group(main-header-text)
   └─ …

Maar nogmaals, we gebruiken gewoon de standaardinstellingen:

Schuifbare koptekst. Minimale demo . Broncode .

Nu schuift de koptekst een beetje opzij om ruimte te maken voor de terugknop.


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

Browser Support

  • Chrome: 125.
  • Rand: 125.
  • Firefox: 144.
  • Safari: 18.2.

Source

Stel, je hebt een overgangsweergave met een aantal kaarten, maar ook een titel op de pagina. Om alle kaarten behalve de titel te animeren, moet je een selector schrijven die elke afzonderlijke kaart selecteert.

h1 {
    view-transition-name: title;
}
::view-transition-group(title) {
    animation-timing-function: ease-in-out;
}

#card1 { view-transition-name: card1; }
#card2 { view-transition-name: card2; }
#card3 { view-transition-name: card3; }
#card4 { view-transition-name: card4; }

#card20 { view-transition-name: card20; }

::view-transition-group(card1),
::view-transition-group(card2),
::view-transition-group(card3),
::view-transition-group(card4),

::view-transition-group(card20) {
    animation-timing-function: var(--bounce);
}

Heb je 20 elementen? Dan moet je 20 selectors schrijven. Voeg je een nieuw element toe? Dan moet je ook de selector die de animatiestijlen toepast, aanpassen. Niet echt schaalbaar.

De view-transition-class kan in de view transition pseudo-elementen worden gebruikt om dezelfde stijlregel toe te passen.

#card1 { view-transition-name: card1; }
#card2 { view-transition-name: card2; }
#card3 { view-transition-name: card3; }
#card4 { view-transition-name: card4; }
#card5 { view-transition-name: card5; }

#card20 { view-transition-name: card20; }

#cards-wrapper > div {
  view-transition-class: card;
}
html::view-transition-group(.card) {
  animation-timing-function: var(--bounce);
}

Het volgende voorbeeld met kaarten maakt gebruik van het vorige CSS-fragment. Alle kaarten – inclusief nieuw toegevoegde kaarten – krijgen dezelfde timing met één selector: html::view-transition-group(.card) .

Opname van de Cards-demo . Door gebruik te maken van view-transition-class wordt dezelfde animation-timing-function toegepast op alle kaarten, behalve op de toegevoegde of verwijderde kaarten.

Debug-overgangen

Omdat overgangen tussen weergaven gebaseerd zijn op CSS-animaties, is het paneel Animaties in Chrome DevTools uitstekend geschikt voor het debuggen van overgangen.

Via het paneel Animaties kunt u de volgende animatie pauzeren en vervolgens heen en weer door de animatie scrollen. Tijdens dit proces vindt u de overgangs-pseudo-elementen in het paneel Elementen .

Foutopsporing van weergaveovergangen met Chrome DevTools.

De elementen die tijdens een overgang verschijnen, hoeven niet hetzelfde DOM-element te zijn.

Tot nu toe hebben we view-transition-name gebruikt om aparte overgangselementen te creëren voor de header en de tekst in de header. Conceptueel gezien zijn dit dezelfde elementen vóór en na de DOM-wijziging, maar je kunt overgangen creëren waarbij dat niet het geval is.

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

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

Als er vervolgens op de miniatuur wordt geklikt, kan deze dezelfde view-transition-name krijgen, maar alleen voor de duur van de overgang:

thumbnail.onclick = async () => {
  thumbnail.style.viewTransitionName = 'full-embed';

  document.startViewTransition(() => {
    thumbnail.style.viewTransitionName = '';
    updateTheDOMSomehow();
  });
};

En het resultaat:

Een element dat overgaat in een ander. Minimale demo . Bron .

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

De daadwerkelijke code voor deze overgang is iets complexer dan het voorgaande voorbeeld, omdat deze ook de overgang terug naar de miniatuurpagina afhandelt. Zie de broncode voor de volledige implementatie.


Aangepaste in- en uitgangen

Kijk eens naar dit voorbeeld:

Zijbalk openen en sluiten. Minimale demo . Broncode .

De zijbalk maakt deel uit van de overgang:

.sidebar {
  view-transition-name: sidebar;
}

Maar in tegenstelling tot de header in het vorige voorbeeld, verschijnt de zijbalk niet op alle pagina's. Als beide staten de zijbalk hebben, zien de overgangspseudo-elementen er als volgt uit:

::view-transition
├─ …other transition groups…
└─ ::view-transition-group(sidebar)
   └─ ::view-transition-image-pair(sidebar)
      ├─ ::view-transition-old(sidebar)
      └─ ::view-transition-new(sidebar)

Als de zijbalk echter alleen op de nieuwe pagina staat, zal het pseudo-element ::view-transition-old(sidebar) er niet zijn. Omdat er geen 'oude' afbeelding voor de zijbalk is, zal het afbeeldingspaar alleen een ::view-transition-new(sidebar) bevatten. Op dezelfde manier zal het afbeeldingspaar, als de zijbalk alleen op de oude pagina staat, alleen een ::view-transition-old(sidebar) bevatten.

In de vorige demo zag je dat de overgang van de zijbalk anders was, afhankelijk van of deze verscheen, verdween of in beide toestanden aanwezig was. Bij het verschijnen schuift de zijbalk van rechts naar binnen en vervaagt hij, bij het verdwijnen schuift hij naar rechts naar buiten en vervaagt hij, en blijft hij op zijn plaats wanneer hij in beide toestanden aanwezig is.

Om specifieke overgangen voor het in- en uitgaan van een afbeelding te creëren, kunt u de pseudo-klasse :only-child gebruiken om de oude of nieuwe pseudo-elementen te selecteren wanneer dit het enige kindelement in het afbeeldingspaar is:

/* Entry transition */
::view-transition-new(sidebar):only-child {
  animation: 300ms cubic-bezier(0, 0, 0.2, 1) both fade-in,
    300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}

/* Exit transition */
::view-transition-old(sidebar):only-child {
  animation: 150ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
    300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-right;
}

In dit geval is er geen specifieke overgang voor het geval de zijbalk in beide staten aanwezig is, aangezien de standaardinstelling perfect is.

Asynchrone DOM-updates en wachten op inhoud

De callback die aan .startViewTransition() wordt doorgegeven, kan een promise retourneren, waardoor asynchrone DOM-updates mogelijk zijn en er gewacht kan worden tot belangrijke content gereed is.

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

De overgang wordt pas gestart als de belofte is vervuld. Gedurende deze tijd is de pagina bevroren, dus vertragingen moeten tot een minimum beperkt worden. Concreet moeten netwerkverzoeken worden uitgevoerd vóórdat .startViewTransition() wordt aangeroepen, terwijl de pagina nog volledig interactief is, in plaats van ze uit te voeren als onderdeel van de .startViewTransition() -callback.

Als u ervoor kiest te wachten tot afbeeldingen of lettertypen gereed zijn, zorg er dan voor dat u een ruime time-out instelt:

const wait = ms => new Promise(r => setTimeout(r, ms));

document.startViewTransition(async () => {
  updateTheDOMSomehow();

  // Pause for up to 100ms for fonts to be ready:
  await Promise.race([document.fonts.ready, wait(100)]);
});

In sommige gevallen is het echter beter om de vertraging helemaal te vermijden en de inhoud te gebruiken die je al hebt.


Haal het maximale uit de content die je al hebt.

In het geval dat de miniatuur overgaat in een grotere afbeelding:

De miniatuurafbeelding wordt vergroot. Probeer de demosite .

De standaardovergang is een crossfade, wat betekent dat de miniatuurafbeelding kan overgaan in een nog niet geladen volledige afbeelding.

Een manier om dit aan te pakken is te wachten tot de volledige afbeelding is geladen voordat de overgang begint. Idealiter zou dit gebeuren vóór het aanroepen van .startViewTransition() , zodat de pagina interactief blijft en een laadindicator kan worden weergegeven om de gebruiker te laten weten dat er wordt geladen. Maar in dit geval is er een betere manier:

::view-transition-old(full-embed),
::view-transition-new(full-embed) {
  /* Prevent the default animation,
  so both views remain opacity:1 throughout the transition */
  animation: none;
  /* Use normal blending,
  so the new view sits on top and obscures the old view */
  mix-blend-mode: normal;
}

De miniatuur verdwijnt nu niet meer, maar blijft onder de volledige afbeelding staan. Dit betekent dat als de nieuwe weergave nog niet geladen is, de miniatuur tijdens de overgang zichtbaar blijft. Hierdoor kan de overgang direct starten en kan de volledige afbeelding op zijn eigen tempo laden.

Dit zou niet werken als de nieuwe weergave transparantie zou bevatten, maar in dit geval weten we dat dit niet zo is, dus kunnen we deze optimalisatie doorvoeren.

Wijzigingen in de beeldverhouding verwerken

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

Een element gaat over in een ander, met een verandering in de beeldverhouding. Minimale demo . Bron .

Bij de standaardovergang animeert de groep van de oorspronkelijke grootte naar de nieuwe grootte. Zowel de oude als de nieuwe weergave hebben de volledige breedte van de groep en een automatische hoogte, wat betekent dat de beeldverhouding behouden blijft, ongeacht de grootte van de groep.

Dit is een prima standaardinstelling, maar niet wat in dit geval gewenst is. Dus:

::view-transition-old(full-embed),
::view-transition-new(full-embed) {
  /* Prevent the default animation,
  so both views remain opacity:1 throughout the transition */
  animation: none;
  /* Use normal blending,
  so the new view sits on top and obscures the old view */
  mix-blend-mode: normal;
  /* Make the height the same as the group,
  meaning the view size might not match its aspect-ratio. */
  height: 100%;
  /* Clip any overflow of the view */
  overflow: clip;
}

/* The old view is the thumbnail */
::view-transition-old(full-embed) {
  /* Maintain the aspect ratio of the view,
  by shrinking it to fit within the bounds of the element */
  object-fit: contain;
}

/* The new view is the full image */
::view-transition-new(full-embed) {
  /* Maintain the aspect ratio of the view,
  by growing it to cover the bounds of the element */
  object-fit: cover;
}

Dit betekent dat de miniatuur in het midden van het element blijft staan ​​terwijl de breedte toeneemt, maar de volledige afbeelding 'niet wordt bijgesneden' wanneer deze overgaat van 1:1 naar 16:9.

Voor meer gedetailleerde informatie kunt u terecht bij Weergaveovergangen: Omgaan met wijzigingen in de beeldverhouding.


Gebruik mediaqueries om overgangen aan te passen aan verschillende apparaatstatussen.

Mogelijk wilt u verschillende overgangen gebruiken voor mobiele apparaten en desktops, zoals in dit voorbeeld waarbij op mobiel een volledige zijwaartse beweging wordt gemaakt, maar op desktop een subtielere beweging:

Een element dat overgaat in een ander. Minimale demo . Bron .

Dit kan worden bereikt met behulp van reguliere mediaqueries:

/* Transitions for mobile */
::view-transition-old(root) {
  animation: 300ms ease-out both full-slide-to-left;
}

::view-transition-new(root) {
  animation: 300ms ease-out both full-slide-from-right;
}

@media (min-width: 500px) {
  /* Overrides for larger displays.
  This is the shared axis transition from earlier in the article. */
  ::view-transition-old(root) {
    animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
      300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
  }

  ::view-transition-new(root) {
    animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
      300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
  }
}

U kunt er ook voor kiezen om te wijzigen aan welke elementen u een view-transition-name toewijst, afhankelijk van overeenkomende mediaqueries.


Reageer op de voorkeur voor 'minder beweging'.

Gebruikers kunnen via hun besturingssysteem aangeven dat ze de voorkeur geven aan minder beweging, en die voorkeur wordt weergegeven in CSS .

U kunt ervoor kiezen om alle overgangen voor deze gebruikers te blokkeren:

@media (prefers-reduced-motion) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

Een voorkeur voor 'minder beweging' betekent echter niet dat de gebruiker helemaal geen beweging wil. In plaats van het voorgaande fragment zou je een subtielere animatie kunnen kiezen, maar wel een die de relatie tussen elementen en de gegevensstroom weergeeft.


Beheer meerdere weergaveovergangsstijlen met weergaveovergangstypen.

Browser Support

  • Chrome: 125.
  • Rand: 125.
  • Firefox: 144.
  • Safari: 18.

Source

Soms vereist de overgang van de ene weergave naar de andere een specifiek aangepaste overgang. Bijvoorbeeld, wanneer je naar de volgende of vorige pagina in een paginering gaat, wil je de inhoud wellicht in een andere richting verschuiven, afhankelijk van of je naar een hogere of lagere pagina in de reeks gaat.

Opname van de pagineringsdemo . Deze demo gebruikt verschillende overgangen, afhankelijk van de pagina waarnaar je gaat.

Hiervoor kunt u overgangstypen gebruiken, waarmee u een of meer typen kunt toewijzen aan een actieve overgang. Bijvoorbeeld: gebruik het type forwards bij het overgaan naar een hogere pagina in een paginering en het type ' backwards bij het overgaan naar een lagere pagina. Deze typen zijn alleen actief tijdens het vastleggen of uitvoeren van een overgang, en elk type kan via CSS worden aangepast om verschillende animaties te gebruiken.

Om typen te gebruiken bij een overgang tussen weergaven binnen hetzelfde document, geef je types door aan de startViewTransition methode. Hiervoor accepteert document.startViewTransition ook een object: update is de callbackfunctie die de DOM bijwerkt, en types is een array met de typen.

const direction = determineBackwardsOrForwards();

const t = document.startViewTransition({
  update: updateTheDOMSomehow,
  types: ['slide', direction],
});

Om op deze typen te reageren, gebruikt u de selector :active-view-transition-type() . Geef het type waarop u zich wilt richten door aan de selector. Hiermee kunt u de stijlen van meerdere weergaveovergangen van elkaar gescheiden houden, zonder dat de declaraties van de ene de declaraties van de andere beïnvloeden.

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

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

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

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

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

In de volgende pagineringsdemo schuift de pagina-inhoud naar voren of naar achteren, afhankelijk van het paginanummer waarnaar u navigeert. Het type wordt bepaald bij een klik, waarna het wordt doorgegeven aan document.startViewTransition .

Om een ​​actieve weergaveovergang te selecteren, ongeacht het type, kunt u in plaats daarvan de pseudo-klasse selector :active-view-transition gebruiken.

html:active-view-transition {
    
}

Beheer meerdere weergaveovergangsstijlen met een klassenaam op de basis van de weergaveovergang.

Soms vereist de overgang van het ene type weergave naar het andere een specifiek aangepaste overgang. Of, een 'terug'-navigatie moet anders zijn dan een 'vooruit'-navigatie.

Verschillende overgangen bij het teruggaan. Minimale demo . Broncode .

Voordat overgangstypen bestonden, werd er in deze gevallen tijdelijk een klassenaam aan de overgangsroot toegekend. Bij het aanroepen van document.startViewTransition is deze overgangsroot het <html> -element, dat toegankelijk is via document.documentElement in JavaScript:

if (isBackNavigation) {
  document.documentElement.classList.add('back-transition');
}

const transition = document.startViewTransition(() =>
  updateTheDOMSomehow(data)
);

try {
  await transition.finished;
} finally {
  document.documentElement.classList.remove('back-transition');
}

Om de klassen te verwijderen nadat de overgang is voltooid, gebruikt dit voorbeeld transition.finished , een promise die wordt opgelost zodra de overgang de eindtoestand heeft bereikt. Andere eigenschappen van dit object worden beschreven in de API-referentie .

Nu kun je die klassenaam in je CSS gebruiken om de overgang aan te passen:

/* 'Forward' transitions */
::view-transition-old(root) {
  animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
    300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
}

::view-transition-new(root) {
  animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in, 300ms
      cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}

/* Overrides for 'back' transitions */
.back-transition::view-transition-old(root) {
  animation-name: fade-out, slide-to-right;
}

.back-transition::view-transition-new(root) {
  animation-name: fade-in, slide-from-left;
}

Net als bij mediaqueries kan de aanwezigheid van deze klassen ook worden gebruikt om te wijzigen welke elementen een view-transition-name krijgen.


Voer overgangen uit zonder andere animaties te blokkeren.

Bekijk deze demo van een video met een overgangspositie:

Video-overgang. Minimale demo . Bron .

Zag je er iets mis mee? Geen zorgen als dat niet zo is. Hier is het in slow motion te bekijken:

Video-overgang, langzamer. Minimale demo . Bron .

Tijdens de overgang lijkt de video even stil te staan, waarna de afspelende versie van de video geleidelijk verschijnt. Dit komt doordat ::view-transition-old(video) een screenshot is van de oude weergave, terwijl ::view-transition-new(video) een livebeeld is van de nieuwe weergave.

Je kunt dit oplossen, maar vraag jezelf eerst af of het de moeite waard is. Als je het 'probleem' niet zag toen de overgang op normale snelheid werd afgespeeld, zou ik er niet aan beginnen.

Als je het echt wilt oplossen, laat dan niet ::view-transition-old(video) zien, maar schakel direct over naar ::view-transition-new(video) . Je kunt dit doen door de standaardstijlen en -animaties te overschrijven:

::view-transition-old(video) {
  /* Don't show the frozen old view */
  display: none;
}

::view-transition-new(video) {
  /* Don't fade the new view in */
  animation: none;
}

En dat is alles!

Video-overgang, langzamer. Minimale demo . Bron .

Nu wordt de video tijdens de overgang afgespeeld.


Integratie met de Navigation API (en andere frameworks)

View-transities worden zo gespecificeerd dat ze kunnen worden geïntegreerd met andere frameworks of libraries. Als uw single-page application (SPA) bijvoorbeeld een router gebruikt, kunt u het update-mechanisme van de router aanpassen om de content bij te werken met behulp van een view-transitie.

In het volgende codefragment, afkomstig uit deze pagineringsdemo, wordt de interceptiehandler van de Navigation API aangepast om document.startViewTransition aan te roepen wanneer weergaveovergangen worden ondersteund.

navigation.addEventListener("navigate", (e) => {
    // Don't intercept if not needed
    if (shouldNotIntercept(e)) return;

    // Intercept the navigation
    e.intercept({
        handler: async () => {
            // Fetch the new content
            const newContent = await fetchNewContent(e.destination.url, {
                signal: e.signal,
            });

            // The UA does not support View Transitions, or the UA
            // already provided a Visual Transition by itself (e.g. swipe back).
            // In either case, update the DOM directly
            if (!document.startViewTransition || e.hasUAVisualTransition) {
                setContent(newContent);
                return;
            }

            // Update the content using a View Transition
            const t = document.startViewTransition(() => {
                setContent(newContent);
            });
        }
    });
});

Sommige, maar niet alle, browsers bieden een eigen overgang aan wanneer de gebruiker een veegbeweging maakt om te navigeren. In dat geval moet u geen eigen weergaveovergang activeren, omdat dit tot een slechte of verwarrende gebruikerservaring zou leiden. De gebruiker zou dan twee overgangen zien – één van de browser en één van u – die na elkaar worden uitgevoerd.

Het is daarom aan te raden te voorkomen dat een weergaveovergang start wanneer de browser zelf een visuele overgang heeft verzorgd. Om dit te bereiken, controleert u de waarde van de eigenschap hasUAVisualTransition van het NavigateEvent exemplaar. Deze eigenschap is ingesteld op true wanneer de browser een visuele overgang heeft verzorgd. Deze eigenschap hasUIVisualTransition bestaat ook voor PopStateEvent exemplaren.

In het vorige codefragment wordt bij de controle of de weergaveovergang moet worden uitgevoerd rekening gehouden met deze eigenschap. Wanneer er geen ondersteuning is voor weergaveovergangen binnen hetzelfde document, of wanneer de browser al een eigen overgang heeft geboden, wordt de weergaveovergang overgeslagen.

if (!document.startViewTransition || e.hasUAVisualTransition) {
  setContent(newContent);
  return;
}

In de volgende opname veegt de gebruiker om terug te gaan naar de vorige pagina. De opname links bevat geen controle op de hasUAVisualTransition -vlag. De opname rechts bevat deze controle wel, waardoor de handmatige weergaveovergang wordt overgeslagen omdat de browser een visuele overgang biedt.

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

Animeren met JavaScript

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

Cirkelovergang. Minimale demo . Bron .

Een aantal onderdelen van deze overgang kan niet uitsluitend met CSS worden gerealiseerd:

  • De animatie start vanaf de kliklocatie.
  • De animatie eindigt met een cirkel die een straal heeft tot aan de verste hoek. Hopelijk wordt dit in de toekomst wel mogelijk met CSS.

Gelukkig kun je overgangen maken met behulp van de Web Animation API !

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

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

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

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

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

Dit voorbeeld maakt gebruik van transition.ready , een promise die wordt opgelost zodra de overgangspseudo-elementen succesvol zijn aangemaakt. Andere eigenschappen van dit object worden beschreven in de API-referentie .


Overgangen als verbetering

De View Transition API is ontworpen om een ​​DOM-wijziging te 'verpakken' en er een overgang voor te creëren. De overgang moet echter worden beschouwd als een verbetering; uw app mag dus niet in een 'foutstatus' terechtkomen als de DOM-wijziging slaagt, maar de overgang mislukt. Idealiter zou de overgang niet moeten mislukken, maar als dat wel gebeurt, mag dit de rest van de gebruikerservaring niet verstoren.

Om overgangen als een verbetering te beschouwen, moet je ervoor zorgen dat je overgangsbeloftes niet gebruikt op een manier die ervoor zorgt dat je app een foutmelding geeft als de overgang mislukt.

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() een foutmelding geeft als de overgang de ready status niet kan bereiken, maar dat betekent niet dat de weergave niet correct is overgeschakeld. De DOM is mogelijk wel succesvol bijgewerkt, maar er waren dubbele view-transition-name , waardoor de overgang werd overgeslagen.

In plaats van:

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 gebruikt transition.updateCallbackDone om te wachten op de DOM-update en om de actie te weigeren als deze mislukt. switchView weigert niet langer als de overgang mislukt, maar wordt opgelost wanneer de DOM-update is voltooid en weigert de actie als deze mislukt.

Als je wilt dat switchView wordt voltooid wanneer de nieuwe weergave 'stabiel' is, oftewel wanneer een geanimeerde overgang is voltooid of naar het einde is overgeslagen, vervang dan transition.updateCallbackDone door transition.finished .


Geen polyestervulling, maar…

Dit is geen eenvoudige functionaliteit om te polyfillen. Deze hulpfunctie maakt het echter een stuk gemakkelijker in browsers die geen view transitions ondersteunen:

function transitionHelper({
  skipTransition = false,
  types = [],
  update,
}) {

  const unsupported = (error) => {
    const updateCallbackDone = Promise.resolve(update()).then(() => {});

    return {
      ready: Promise.reject(Error(error)),
      updateCallbackDone,
      finished: updateCallbackDone,
      skipTransition: () => {},
      types,
    };
  }

  if (skipTransition) {
    return unsupported('skipTransition was set to true');
  }

  if (!document.startViewTransition) {
    return unsupported('Same-Document View Transitions are not supported in this browser');
  }

  if (!ViewTransition || !("types" in ViewTransition.prototype)) {
    return unsupported('View Transitions with types are not supported in this browser');
  }

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

  return transition;
}

En het kan als volgt worden gebruikt:

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

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

  // …
}

In browsers die geen weergaveovergangen ondersteunen, wordt updateDOM nog steeds aangeroepen, maar er vindt geen geanimeerde overgang plaats.

Je kunt ook classNames opgeven die tijdens de overgang aan <html> worden toegevoegd, waardoor het gemakkelijker wordt om de overgang aan te passen aan het type navigatie .

Je kunt ook true doorgeven aan skipTransition als je geen animatie wilt, zelfs niet in browsers die weergaveovergangen ondersteunen. Dit is handig als gebruikers op je site de voorkeur geven aan het uitschakelen van overgangen.


Werken met frameworks

Als je werkt met een bibliotheek of framework dat DOM-wijzigingen abstraheert, is het lastige om te weten wanneer de DOM-wijziging voltooid is. Hier volgen een aantal voorbeelden, waarin de bovenstaande helperfunctie wordt gebruikt, in verschillende frameworks.

  • React — de sleutel hier is flushSync , waarmee een reeks statuswijzigingen synchroon wordt toegepast. Ja, er is een grote waarschuwing over het gebruik van die API, maar Dan Abramov verzekert me dat het in dit geval gepast is. Zoals gebruikelijk bij React en asynchrone code, moet je er bij het gebruik van de verschillende promises die door startViewTransition worden geretourneerd, voor zorgen dat je code met de juiste status wordt uitgevoerd.
  • In Vue.js is nextTick hier de sleutel, die wordt uitgevoerd zodra de DOM is bijgewerkt.
  • Svelte is erg vergelijkbaar met Vue, maar de methode om op de volgende wijziging te wachten is tick .
  • Let op — de sleutel hier is de this.updateComplete -promise binnen componenten, die wordt vervuld zodra de DOM is bijgewerkt.
  • Angular — de sleutel hier is applicationRef.tick , waarmee openstaande DOM-wijzigingen worden doorgestuurd. Vanaf Angular versie 17 kun je withViewTransitions gebruiken, dat bij @angular/router wordt geleverd .

API-referentie

const viewTransition = document.startViewTransition(update)

Start een nieuwe ViewTransition .

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

Als de belofte die door updateCallback wordt geretourneerd, wordt vervuld, begint de overgang in het volgende frame. Als de belofte die door updateCallback wordt geretourneerd, wordt afgewezen, wordt de overgang 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. Deze is aanvankelijk leeg. Zie viewTransition.types verderop voor meer informatie.

Instantieleden van ViewTransition :

viewTransition.updateCallbackDone

Een belofte die wordt vervuld wanneer de belofte die door updateCallback wordt geretourneerd, wordt vervuld, of wordt afgewezen wanneer deze wordt afgewezen.

De View Transition API verpakt een DOM-wijziging en creëert een overgang. Soms is het echter niet belangrijk of de overgangsanimatie slaagt of mislukt, maar wilt u alleen weten of en wanneer de DOM-wijziging plaatsvindt. Voor dat specifieke geval is updateCallbackDone .

viewTransition.ready

Een belofte die wordt ingelost zodra de pseudo-elementen voor de overgang zijn aangemaakt en de animatie op het punt staat te beginnen.

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

Dit is handig voor het animeren van overgangs-pseudo-elementen met JavaScript .

viewTransition.finished

Een belofte die wordt ingelost zodra de eindtoestand volledig zichtbaar en interactief is voor de gebruiker.

Het wordt alleen afgewezen als updateCallback een afgewezen promise retourneert, omdat dit aangeeft dat de eindtoestand niet is gecreëerd.

Als een overgang niet start of wordt overgeslagen tijdens de overgang, wordt de eindtoestand alsnog bereikt, waardoor de status ' finished voldoet.

viewTransition.types

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

Om in CSS op een specifiek type te reageren, gebruik je de pseudo-klasse selector :active-view-transition-type(type) op de overgangsroot.

De gegevenstypen worden automatisch opgeruimd wanneer de overgang tussen weergaven is voltooid.

viewTransition.skipTransition()

Sla het animatiegedeelte van de overgang over.

Hierdoor wordt de aanroep updateCallback niet overgeslagen, aangezien de DOM-wijziging losstaat van de overgang.


Standaardstijl en overgangsreferentie

::view-transition
Het root pseudo-element dat de viewport vult en elke ::view-transition-group bevat.
::view-transition-group

Perfecte locatie.

width en height van de overgangen tussen de 'voor' en 'na' toestanden.

Overgangen transform tussen het 'voor' en 'na' weergavegebied.

::view-transition-image-pair

Perfect gepositioneerd om de groep aan te vullen.

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

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

Precies linksboven op de verpakking.

Vult 100% van de groepsbreedte, maar heeft een automatische hoogte, waardoor de beeldverhouding behouden blijft in plaats van de hele groep te vullen.

Heeft mix-blend-mode: plus-lighter voor een echte crossfade.

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


Feedback

Feedback van ontwikkelaars wordt altijd op prijs gesteld. Dien hiervoor een issue in bij de CSS Working Group op GitHub met suggesties en vragen. Voeg [css-view-transitions] toe aan het begin van je issue.

Mocht je een bug tegenkomen, meld deze dan bij Chromium .