Veröffentlicht: 17. August 2021, letzte Aktualisierung: 25. September 2024
Wenn ein Ansichtsübergang in einem einzelnen Dokument ausgeführt wird, wird er als Ansichtsübergang im selben Dokument bezeichnet. Das ist in der Regel bei Single-Page-Anwendungen (SPAs) der Fall, bei denen JavaScript zum Aktualisieren des DOM verwendet wird. In Chrome 111 und höher werden Übergänge zwischen Ansichten desselben Dokuments unterstützt.
Wenn Sie einen Wechsel zwischen Ansichten im selben Dokument auslösen möchten, rufen Sie document.startViewTransition
auf:
function handleClick(e) {
// Fallback for browsers that don't support this API:
if (!document.startViewTransition) {
updateTheDOMSomehow();
return;
}
// With a View Transition:
document.startViewTransition(() => updateTheDOMSomehow());
}
Wenn die Funktion aufgerufen wird, erfasst der Browser automatisch Snapshots aller Elemente, für die eine view-transition-name
-CSS-Property deklariert wurde.
Anschließend wird der übergebene Callback ausgeführt, der das DOM aktualisiert, und es werden Snapshots des neuen Status erstellt.
Diese Snapshots werden dann in einem Pseudoelement-Baum angeordnet und mithilfe von CSS-Animationen animiert. Paare von Snapshots aus dem alten und dem neuen Zustand wechseln reibungslos von ihrer alten Position und Größe an ihren neuen Ort, während ihre Inhalte überblendet werden. Sie können die Animationen auch mit CSS anpassen.
Standardübergang: Weichzeichner
Der Standardübergang zwischen den Ansichten ist ein Cross-Fade, was eine gute Einführung in die API darstellt:
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));
}
Dabei ändert updateTheDOMSomehow
das DOM in den neuen Status. Das ist ganz Ihnen überlassen. Sie können beispielsweise Elemente hinzufügen oder entfernen, Klassennamen ändern oder Stile ändern.
So einfach gehts:
Okay, ein Cross-Fade ist nicht so beeindruckend. Zum Glück können Übergänge angepasst werden. Aber zuerst musst du wissen, wie dieser einfache Cross-Fade funktioniert.
Funktionsweise dieser Übergänge
Aktualisieren wir das vorherige Codebeispiel.
document.startViewTransition(() => updateTheDOMSomehow(data));
Wenn .startViewTransition()
aufgerufen wird, erfasst die API den aktuellen Status der Seite. Dazu gehört auch das Erstellen eines Snapshots.
Danach wird der an .startViewTransition()
übergebene Callback aufgerufen. Dort wird das DOM geändert. Anschließend erfasst die API den neuen Status der Seite.
Sobald der neue Status erfasst wurde, erstellt die API einen Pseudo-Elementbaum in dieser Form:
::view-transition
└─ ::view-transition-group(root)
└─ ::view-transition-image-pair(root)
├─ ::view-transition-old(root)
└─ ::view-transition-new(root)
Das ::view-transition
befindet sich in einem Overlay, das über allem anderen auf der Seite liegt. Das ist nützlich, wenn Sie eine Hintergrundfarbe für den Übergang festlegen möchten.
::view-transition-old(root)
ist ein Screenshot der alten Ansicht und ::view-transition-new(root)
eine Live-Darstellung der neuen Ansicht. Beide werden als CSS-Ersatzinhalt gerendert (z. B. ein <img>
).
Die alte Ansicht wird von opacity: 1
nach opacity: 0
animiert, während die neue Ansicht von opacity: 0
nach opacity: 1
animiert wird. Dadurch entsteht ein Überblendungseffekt.
Die gesamte Animation wird mit CSS-Animationen ausgeführt und kann daher mit CSS angepasst werden.
Übergang anpassen
Alle Pseudoelemente für Ansichtsübergänge können mit CSS ausgerichtet werden. Da die Animationen mit CSS definiert sind, können Sie sie mithilfe vorhandener CSS-Animationseigenschaften ändern. Beispiel:
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 5s;
}
Durch diese eine Änderung ist der Übergang jetzt sehr langsam:
Okay, das ist immer noch nicht beeindruckend. Stattdessen wird im folgenden Code der Übergang mit gemeinsamer Achse von Material Design implementiert:
@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;
}
Das Ergebnis:
Mehrere Elemente überblenden
In der vorherigen Demo ist die gesamte Seite am Übergang mit gemeinsamer Achse beteiligt. Das funktioniert für den Großteil der Seite, aber nicht ganz für die Überschrift, da sie heraus- und wieder hineingeschoben wird.
Um dies zu vermeiden, können Sie die Kopfzeile aus dem Rest der Seite extrahieren, damit sie separat animiert werden kann. Dazu weisen Sie dem Element eine view-transition-name
zu.
.main-header {
view-transition-name: main-header;
}
Der Wert von view-transition-name
kann beliebig sein, mit Ausnahme von none
, was bedeutet, dass es keinen Übergangsnamen gibt. Damit wird das Element eindeutig über den gesamten Übergang hinweg identifiziert.
Das Ergebnis:
Jetzt bleibt die Überschrift an Ort und Stelle und wird überblendet.
Diese CSS-Deklaration hat dazu geführt, dass sich der Pseudoelementbaum geändert hat:
::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)
Es gibt jetzt zwei Übergangsgruppen. Eine für die Überschrift und eine für den Rest. Sie können unabhängig mit CSS ausgerichtet und unterschiedliche Übergänge zugewiesen werden. In diesem Fall wurde für main-header
der Standardübergang beibehalten, also ein Cross-Fade.
Der Standardübergang ist nicht nur ein Cross-Fade, sondern auch ein ::view-transition-group
-Übergang:
- Positionieren und transformieren (mit einem
transform
) - Breite
- Höhe
Das war bisher nicht wichtig, da die Kopfzeile auf beiden Seiten des DOM dieselbe Größe und Position hat. Sie können aber auch den Text in der Kopfzeile extrahieren:
.main-header-text {
view-transition-name: main-header-text;
width: fit-content;
}
Mit fit-content
wird festgelegt, dass das Element die Größe des Texts hat, anstatt sich auf die verbleibende Breite auszudehnen. Andernfalls wird das Textelement der Überschrift durch den Zurückpfeil auf beiden Seiten unterschiedlich groß.
Wir haben also drei Teile:
::view-transition
├─ ::view-transition-group(root)
│ └─ …
├─ ::view-transition-group(main-header)
│ └─ …
└─ ::view-transition-group(main-header-text)
└─ …
Aber auch hier: Wir verwenden einfach die Standardeinstellungen:
Jetzt gleitet der Text der Überschrift ein wenig zur Seite, um Platz für die Schaltfläche „Zurück“ zu schaffen.
Mehrere Pseudoelemente mit view-transition-class
auf dieselbe Weise animieren
Unterstützte Browser
Angenommen, Sie haben einen Ansichtsübergang mit mehreren Karten, aber auch einen Titel auf der Seite. Wenn Sie alle Karten außer dem Titel animieren möchten, müssen Sie einen Selector schreiben, der auf jede einzelne Karte ausgerichtet ist.
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);
}
20 Elemente? Das sind 20 Selektoren, die Sie schreiben müssen. Fügen Sie ein neues Element hinzu? Außerdem müssen Sie die Auswahl, mit der die Animationsstile angewendet werden, vergrößern. Nicht wirklich skalierbar.
Die view-transition-class
kann in den Pseudoelementen für die Ansichtsübergänge verwendet werden, um dieselbe Stilregel anzuwenden.
#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);
}
Im Beispiel für Karten wird das vorherige CSS-Snippet verwendet. Auf alle Karten, einschließlich neu hinzugefügter, wird mit einer einzigen Auswahl das gleiche Timing angewendet: html::view-transition-group(.card)
.
Übergänge debuggen
Da Ansichtsübergänge auf CSS-Animationen basieren, eignet sich der Bereich Animationen in den Chrome-Entwicklertools hervorragend zum Debuggen von Übergängen.
Im Bereich Animationen können Sie die nächste Animation pausieren und dann vor- und zurückspulen. Die Pseudoelemente für Übergänge finden Sie im Bereich Elemente.
Die Elemente, zwischen denen die Übergänge erfolgen, müssen nicht dasselbe DOM-Element sein.
Bisher haben wir view-transition-name
verwendet, um separate Übergangselemente für den Header und den Text im Header zu erstellen. Vor und nach der DOM-Änderung handelt es sich um dasselbe Element. Sie können aber Übergänge erstellen, bei denen das nicht der Fall ist.
Dem Hauptvideo-Embed kann beispielsweise view-transition-name
zugewiesen werden:
.full-embed {
view-transition-name: full-embed;
}
Wenn dann auf das Thumbnail geklickt wird, kann ihm dieselbe view-transition-name
zugewiesen werden, aber nur für die Dauer des Übergangs:
thumbnail.onclick = async () => {
thumbnail.style.viewTransitionName = 'full-embed';
document.startViewTransition(() => {
thumbnail.style.viewTransitionName = '';
updateTheDOMSomehow();
});
};
Das Ergebnis:
Das Thumbnail wird jetzt in das Hauptbild überführt. Obwohl es sich um konzeptionell (und wörtlich) unterschiedliche Elemente handelt, werden sie von der Transition API als dasselbe Element behandelt, da sie dieselbe view-transition-name
haben.
Der tatsächliche Code für diesen Übergang ist etwas komplizierter als das vorherige Beispiel, da er auch den Übergang zurück zur Miniaturansichtsseite steuert. Die vollständige Implementierung finden Sie in der Quelle.
Benutzerdefinierte Übergänge für den Einstieg und Ausstieg
Sehen Sie sich dieses Beispiel an:
Die Seitenleiste ist Teil der Umstellung:
.sidebar {
view-transition-name: sidebar;
}
Im Gegensatz zum Header im vorherigen Beispiel wird die Seitenleiste jedoch nicht auf allen Seiten angezeigt. Wenn beide Status die Seitenleiste haben, sehen die Pseudoelemente für den Übergang so aus:
::view-transition
├─ …other transition groups…
└─ ::view-transition-group(sidebar)
└─ ::view-transition-image-pair(sidebar)
├─ ::view-transition-old(sidebar)
└─ ::view-transition-new(sidebar)
Wenn sich die Seitenleiste jedoch nur auf der neuen Seite befindet, ist das Pseudo-Element ::view-transition-old(sidebar)
nicht vorhanden. Da es kein „altes“ Bild für die Seitenleiste gibt, enthält das Bildpaar nur eine ::view-transition-new(sidebar)
. Wenn die Seitenleiste nur auf der alten Seite vorhanden ist, enthält das Bildpaar nur eine ::view-transition-old(sidebar)
.
In der vorherigen Demo wird die Seitenleiste unterschiedlich animiert, je nachdem, ob sie in den oder aus dem jeweiligen Status wechselt oder in beiden Status vorhanden ist. Sie wird von rechts eingeblendet und verblasst, wenn sie wieder ausgeblendet wird.
Wenn Sie bestimmte Ein- und Ausblendübergänge erstellen möchten, können Sie den Pseudo-Klassennamen :only-child
verwenden, um das alte oder neue Pseudoelement als Ziel festzulegen, wenn es das einzige untergeordnete Element im Bildpaar ist:
/* 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 diesem Fall gibt es keinen speziellen Übergang, wenn die Seitenleiste in beiden Status vorhanden ist, da der Standard perfekt ist.
Asynchrone DOM-Aktualisierungen und Warten auf Inhalte
Der an .startViewTransition()
übergebene Callback kann ein Promise zurückgeben, das asynchrone DOM-Aktualisierungen ermöglicht und darauf wartet, dass wichtige Inhalte verfügbar sind.
document.startViewTransition(async () => {
await something;
await updateTheDOMSomehow();
await somethingElse;
});
Die Umstellung wird erst gestartet, wenn die Zusicherung erfüllt ist. Während dieser Zeit ist die Seite eingefroren, sodass Verzögerungen hier auf ein Minimum beschränkt werden sollten. Netzwerkabrufe sollten vor dem Aufruf von .startViewTransition()
erfolgen, während die Seite noch vollständig interaktiv ist, und nicht im Rahmen des .startViewTransition()
-Callbacks.
Wenn Sie warten möchten, bis Bilder oder Schriftarten verfügbar sind, sollten Sie eine kurze Zeitüberschreitung festlegen:
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 einigen Fällen ist es jedoch besser, die Verzögerung zu vermeiden und die vorhandenen Inhalte zu verwenden.
Vorhandene Inhalte optimal nutzen
Wenn das Thumbnail zu einem größeren Bild übergeht:
Der Standardübergang ist ein Überblenden. Das bedeutet, dass die Miniaturansicht mit einem noch nicht geladenen Vollbild überblendet werden kann.
Eine Möglichkeit, dies zu vermeiden, besteht darin, zu warten, bis das vollständige Bild geladen ist, bevor Sie mit dem Übergang beginnen. Idealerweise geschieht dies vor dem Aufrufen von .startViewTransition()
, damit die Seite interaktiv bleibt und ein Ladesymbol angezeigt werden kann, um dem Nutzer zu signalisieren, dass Inhalte geladen werden. In diesem Fall gibt es jedoch eine bessere Lösung:
::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;
}
Jetzt wird das Thumbnail nicht mehr ausgeblendet, sondern liegt einfach unter dem vollständigen Bild. Wenn die neue Ansicht also noch nicht geladen wurde, ist das Thumbnail während der gesamten Umstellung sichtbar. So kann der Übergang sofort beginnen und das vollständige Bild kann in Ruhe geladen werden.
Das würde nicht funktionieren, wenn die neue Ansicht Transparenz hätte. In diesem Fall wissen wir jedoch, dass das nicht der Fall ist, sodass wir diese Optimierung vornehmen können.
Änderungen am Seitenverhältnis verarbeiten
Bisher haben alle Übergänge zu Elementen mit demselben Seitenverhältnis geführt. Das ist aber nicht immer der Fall. Was ist, wenn das Thumbnail ein Seitenverhältnis von 1:1 und das Hauptbild 16:9 hat?
Beim Standardübergang wird die Gruppe von der vorherigen Größe zur nachfolgenden Größe animiert. Die alte und die neue Ansicht haben eine Breite von 100% der Gruppe und eine automatische Höhe. Das bedeutet, dass sie unabhängig von der Größe der Gruppe ihr Seitenverhältnis beibehalten.
Das ist eine gute Standardeinstellung, aber in diesem Fall nicht das, was wir wollen. Das bedeutet:
::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;
}
Das bedeutet, dass das Thumbnail bei der Ausweitung der Breite in der Mitte des Elements bleibt, das vollständige Bild wird jedoch beim Übergang von 1:1 zu 16:9 wieder „entfernt“.
Weitere Informationen finden Sie unter Übergangseffekte: Umgang mit Änderungen des Seitenverhältnisses.
Über Medienabfragen Übergänge für verschiedene Gerätestatus ändern
Sie können unterschiedliche Übergänge für Mobilgeräte und Computer verwenden. In diesem Beispiel wird auf Mobilgeräten eine vollständige seitliche Folie verwendet, auf Computern hingegen eine dezentere Folie:
Das ist mithilfe regulärer Medienabfragen möglich:
/* 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;
}
}
Je nach übereinstimmenden Mediaabfragen können Sie auch ändern, welchen Elementen Sie eine view-transition-name
zuweisen.
Auf die Einstellung „Weniger Bewegung“ reagieren
Nutzer können über ihr Betriebssystem angeben, dass sie weniger Bewegung bevorzugen. Diese Einstellung wird in CSS freigegeben.
Sie können festlegen, dass für diese Nutzer keine Umstellungen erfolgen:
@media (prefers-reduced-motion) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}
Die Einstellung „Eingeschränkte Bewegung“ bedeutet jedoch nicht, dass der Nutzer keine Bewegung wünscht. Anstelle des vorherigen Snippets können Sie eine dezentere Animation auswählen, die aber dennoch die Beziehung zwischen den Elementen und den Datenfluss ausdrückt.
Mehrere Ansichtsübergangsstile mit Ansichtsübergangstypen verarbeiten
Unterstützte Browser
Manchmal sollte ein Übergang von einer bestimmten Ansicht zu einer anderen speziell angepasst sein. Wenn Sie beispielsweise in einer Paginierungssequenz zur nächsten oder zur vorherigen Seite wechseln, können Sie die Inhalte je nachdem, ob Sie zu einer höheren oder niedrigeren Seite der Sequenz wechseln, in eine andere Richtung verschieben.
Dazu können Sie die Typen für Ansichtsübergänge verwenden. Damit können Sie einem aktiven Ansichtsübergang einen oder mehrere Typen zuweisen. Wenn du beispielsweise zu einer höheren Seite in einer Paginierungssequenz wechseln möchtest, verwende den Typ forwards
und wenn du zu einer niedrigeren Seite wechseln möchtest, verwende den Typ backwards
. Diese Typen sind nur aktiv, wenn ein Übergang erfasst oder ausgeführt wird. Jeder Typ kann über CSS angepasst werden, um verschiedene Animationen zu verwenden.
Wenn Sie Typen in einem Ansichtsübergang innerhalb desselben Dokuments verwenden möchten, übergeben Sie types
an die Methode startViewTransition
. Dazu akzeptiert document.startViewTransition
auch ein Objekt: update
ist die Callback-Funktion, die das DOM aktualisiert, und types
ist ein Array mit den Typen.
const direction = determineBackwardsOrForwards();
const t = document.startViewTransition({
update: updateTheDOMSomehow,
types: ['slide', direction],
});
Verwenden Sie die Auswahl :active-view-transition-type()
, um auf diese Typen zu reagieren. Geben Sie die type
, auf die Sie das Targeting vornehmen möchten, in die Auswahl ein. So können Sie die Stile mehrerer Ansichtsübergänge voneinander getrennt halten, ohne dass sich die Deklarationen der einzelnen Übergänge gegenseitig beeinflussen.
Da Typen nur beim Aufzeichnen oder Ausführen des Übergangs angewendet werden, können Sie mit dem Selector ein view-transition-name
für ein Element nur für den Ansichtsübergang mit diesem Typ festlegen oder aufheben.
/* 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 der folgenden Paginierungsdemo wird der Seiteninhalt je nach aufgerufener Seitenzahl vor- oder zurückgeschoben. Die Typen werden beim Klicken bestimmt und an document.startViewTransition
übergeben.
Wenn Sie unabhängig vom Typ eine Ausrichtung auf alle Übergänge zur aktiven Ansicht vornehmen möchten, können Sie stattdessen die Pseudoklasse :active-view-transition
verwenden.
html:active-view-transition {
…
}
Mehrere Ansichtsübergangsstile mit einem Klassennamen im Stammelement des Ansichtsübergangs verarbeiten
Manchmal sollte ein Übergang von einer bestimmten Ansicht zu einer anderen speziell angepasst werden. Oder die Navigationselemente „Zurück“ und „Weiter“ sollten sich voneinander unterscheiden.
Vor Übergangstypen wurde in diesen Fällen vorübergehend ein Klassenname auf der Übergangs-Stammebene festgelegt. Beim Aufrufen von document.startViewTransition
ist dieses Übergangselement das <html>
-Element, auf das in JavaScript mit document.documentElement
zugegriffen werden kann:
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');
}
Um die Klassen nach Abschluss des Übergangs zu entfernen, wird in diesem Beispiel transition.finished
verwendet, ein Versprechen, das erfüllt wird, sobald der Übergang seinen Endstatus erreicht hat. Weitere Eigenschaften dieses Objekts finden Sie in der API-Referenz.
Sie können diesen Klassennamen jetzt in Ihrem CSS verwenden, um die Übergänge zu ändern:
/* '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;
}
Ähnlich wie bei Medienabfragen kann auch anhand der Anwesenheit dieser Klassen festgelegt werden, welche Elemente eine view-transition-name
erhalten.
Übergänge ausführen, ohne andere Animationen einzufrieren
Hier siehst du eine Demo einer Videoübergangsposition:
Haben Sie etwas Ungewöhnliches festgestellt? Keine Sorge, wenn Sie das nicht getan haben. Hier ist es verlangsamt:
Während des Übergangs scheint das Video zu frieren, dann wird die wiedergegebene Version des Videos eingeblendet. Das liegt daran, dass ::view-transition-old(video)
ein Screenshot der alten Ansicht ist, während ::view-transition-new(video)
ein Livebild der neuen Ansicht ist.
Sie können das Problem beheben, aber fragen Sie sich zuerst, ob sich das lohnt. Wenn du das „Problem“ nicht gesehen hast, als der Übergang mit normaler Geschwindigkeit abgespielt wurde, würde ich ihn nicht ändern.
Wenn Sie das Problem wirklich beheben möchten, zeigen Sie die ::view-transition-old(video)
nicht an, sondern wechseln Sie direkt zur ::view-transition-new(video)
. Dazu können Sie die Standardstile und ‑animationen überschreiben:
::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;
}
Webseite.
Jetzt wird das Video während des gesamten Übergangs abgespielt.
Einbindung in die Navigation API (und andere Frameworks)
Ansichtsübergänge werden so angegeben, dass sie in andere Frameworks oder Bibliotheken eingebunden werden können. Wenn Ihre SPA (Single-Page-Anwendung) beispielsweise einen Router verwendet, können Sie den Aktualisierungsmechanismus des Routers so anpassen, dass die Inhalte mithilfe eines Ansichtsübergangs aktualisiert werden.
Im folgenden Code-Snippet aus dieser Paginierungsdemo wird der Abfang-Handler der Navigation API so angepasst, dass document.startViewTransition
aufgerufen wird, wenn Ansichtsübergänge unterstützt werden.
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);
});
}
});
});
Einige, aber nicht alle Browser bieten eine eigene Überblendung, wenn der Nutzer zum Navigieren wischt. In diesem Fall sollten Sie keine eigene Ansichtsübergang auslösen, da dies zu einer schlechten oder verwirrenden Nutzererfahrung führen würde. Der Nutzer sieht dann zwei Übergänge, einen vom Browser und einen von Ihnen, die nacheinander ausgeführt werden.
Daher wird empfohlen, zu verhindern, dass ein Ansichtsübergang gestartet wird, wenn der Browser einen eigenen visuellen Übergang bereitstellt. Prüfen Sie dazu den Wert des Attributs hasUAVisualTransition
der NavigateEvent
-Instanz. Die Property ist auf true
festgelegt, wenn der Browser einen visuellen Übergang bereitgestellt hat. Diese hasUIVisualTransition
-Property ist auch für PopStateEvent
-Instanzen verfügbar.
Im vorherigen Snippet wird diese Eigenschaft bei der Prüfung berücksichtigt, ob die Ansichtsübergang ausgeführt werden soll. Wenn keine Unterstützung für Ansichtsübergänge im selben Dokument vorhanden ist oder der Browser bereits einen eigenen Übergang bereitgestellt hat, wird der Ansichtsübergang übersprungen.
if (!document.startViewTransition || e.hasUAVisualTransition) {
setContent(newContent);
return;
}
In der folgenden Aufzeichnung wischt der Nutzer, um zur vorherigen Seite zurückzukehren. In der Aufnahme auf der linken Seite wird das Flag hasUAVisualTransition
nicht geprüft. Die Aufnahme auf der rechten Seite enthält die Prüfung und überspringt den Übergang zur manuellen Ansicht, da der Browser einen visuellen Übergang bereitgestellt hat.
Animationen mit JavaScript
Bisher wurden alle Übergänge mit CSS definiert. Manchmal reicht CSS jedoch nicht aus:
Einige Teile dieses Übergangs können nicht nur mit CSS erreicht werden:
- Die Animation beginnt an der Klickposition.
- Die Animation endet damit, dass der Kreis einen Radius bis zur entferntesten Ecke hat. In Zukunft wird dies jedoch hoffentlich mit CSS möglich sein.
Zum Glück kannst du mit der Web Animation API Übergänge erstellen.
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 diesem Beispiel wird transition.ready
verwendet, ein Versprechen, das erfüllt wird, sobald die Pseudoelemente für die Übergänge erfolgreich erstellt wurden. Weitere Eigenschaften dieses Objekts finden Sie in der API-Referenz.
Übergänge als Verbesserung
Die View Transition API ist so konzipiert, dass eine DOM-Änderung „verpackt“ und ein Übergang dafür erstellt wird. Der Übergang sollte jedoch als Verbesserung behandelt werden. Ihre App sollte also nicht in den Status „Fehler“ wechseln, wenn die DOM-Änderung erfolgreich ist, der Übergang aber fehlschlägt. Idealerweise sollte der Übergang nicht fehlschlagen. Sollte das doch passieren, sollte die Nutzerfreundlichkeit nicht beeinträchtigt werden.
Damit Übergänge als Verbesserung betrachtet werden können, sollten Sie Transition Promises nicht so verwenden, dass Ihre App bei einem fehlgeschlagenen Übergang abstürzt.
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)', } ); }
Das Problem bei diesem Beispiel ist, dass switchView()
abgelehnt wird, wenn der Übergang nicht den Status ready
erreichen kann. Das bedeutet jedoch nicht, dass der Ansichtswechsel fehlgeschlagen ist. Das DOM wurde möglicherweise erfolgreich aktualisiert, aber es gab doppelte view-transition-name
s, sodass die Umstellung übersprungen wurde.
Gehen Sie in diesem Fall so vor:
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 diesem Beispiel wird transition.updateCallbackDone
verwendet, um auf die DOM-Aktualisierung zu warten und sie bei einem Fehler abzulehnen. switchView
wird nicht mehr abgelehnt, wenn der Übergang fehlschlägt, sondern erst, wenn das DOM-Update abgeschlossen ist. Andernfalls wird er abgelehnt.
Wenn switchView
erst dann aufgelöst werden soll, wenn die neue Ansicht „stabil“ ist, d. h., wenn alle animierten Übergänge abgeschlossen oder zum Ende übersprungen wurden, ersetzen Sie transition.updateCallbackDone
durch transition.finished
.
Keine Polyfill, aber…
Das ist keine einfache Funktion, die man mit Polyfills ersetzen kann. Diese Hilfsfunktion macht die Sache jedoch in Browsern, die keine Ansichtsübergänge unterstützen, viel einfacher:
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');
}
}
So kann es verwendet werden:
function spaNavigate(data) {
const types = isBackNavigation ? ['back-transition'] : [];
const transition = transitionHelper({
update() {
updateTheDOMSomehow(data);
},
types,
});
// …
}
In Browsern, die Ansichtsübergänge nicht unterstützen, wird updateDOM
weiterhin aufgerufen, es erfolgt jedoch keine animierte Überleitung.
Sie können auch einige classNames
angeben, die während des Übergangs zu <html>
hinzugefügt werden sollen. So lässt sich der Übergang je nach Navigationstyp leichter ändern.
Sie können auch true
an skipTransition
übergeben, wenn Sie keine Animation wünschen, auch in Browsern, die Ansichtsübergänge unterstützen. Das ist hilfreich, wenn auf Ihrer Website die Option zum Deaktivieren von Übergängen für Nutzer verfügbar ist.
Mit Frameworks arbeiten
Wenn Sie mit einer Bibliothek oder einem Framework arbeiten, das DOM-Änderungen abstrahiert, ist es schwierig zu erkennen, wann die DOM-Änderung abgeschlossen ist. Hier sind einige Beispiele, in denen der Hilfscode oben in verschiedenen Frameworks verwendet wird.
- React: Der Schlüssel ist hier
flushSync
, mit dem eine Reihe von Statusänderungen synchron angewendet wird. Ja, es gibt eine große Warnung zur Verwendung dieser API, aber Dan Abramov versichert mir, dass sie in diesem Fall angemessen ist. Wie bei React und asynchronem Code üblich, müssen Sie bei der Verwendung der verschiedenen vonstartViewTransition
zurückgegebenen Versprechen darauf achten, dass Ihr Code mit dem richtigen Status ausgeführt wird. - Vue.js: Der Schlüssel ist hier
nextTick
, der erfüllt wird, sobald das DOM aktualisiert wurde. - Svelte: Sehr ähnlich wie Vue, aber die Methode zum Warten auf die nächste Änderung ist
tick
. - Lit: Der Schlüssel ist hier das
this.updateComplete
-Versprechen in den Komponenten, das erfüllt wird, sobald das DOM aktualisiert wurde. - Angular: Der Schlüssel ist hier
applicationRef.tick
, mit dem ausstehende DOM-Änderungen gelöscht werden. Ab Angular-Version 17 können SiewithViewTransitions
verwenden, das in@angular/router
enthalten ist.
API-Referenz
const viewTransition = document.startViewTransition(update)
Starten Sie eine neue
ViewTransition
.update
ist eine Funktion, die aufgerufen wird, sobald der aktuelle Status des Dokuments erfasst wurde.Wenn das von
updateCallback
zurückgegebene Versprechen erfüllt ist, beginnt der Übergang im nächsten Frame. Wenn das vonupdateCallback
zurückgegebene Versprechen abgelehnt wird, wird der Übergang abgebrochen.const viewTransition = document.startViewTransition({ update, types })
Neue
ViewTransition
mit den angegebenen Typen startenupdate
wird aufgerufen, sobald der aktuelle Status des Dokuments erfasst wurde.Mit
types
werden die aktiven Typen für den Übergang festgelegt, wenn der Übergang aufgenommen oder ausgeführt wird. Sie ist anfangs leer. Weitere Informationen finden Sie unten unterviewTransition.types
.
Instanzmitglieder von ViewTransition
:
viewTransition.updateCallbackDone
Ein Versprechen, das erfüllt wird, wenn das von
updateCallback
zurückgegebene Versprechen erfüllt wird, oder abgelehnt wird, wenn es abgelehnt wird.Die View Transition API umschließt eine DOM-Änderung und erstellt einen Übergang. Manchmal ist Ihnen jedoch nicht wichtig, ob die Übergangsanimation erfolgreich war oder nicht. Sie möchten nur wissen, ob und wann die DOM-Änderung erfolgt.
updateCallbackDone
ist für diesen Anwendungsfall vorgesehen.viewTransition.ready
Ein Versprechen, das erfüllt wird, sobald die Pseudoelemente für den Übergang erstellt wurden und die Animation kurz bevorsteht.
Der Übergang wird abgelehnt, wenn er nicht gestartet werden kann. Das kann an einer fehlerhaften Konfiguration liegen, z. B. an doppelten
view-transition-name
s, oder wennupdateCallback
ein abgelehntes Versprechen zurückgibt.Das ist nützlich, um die Pseudoelemente für Übergänge mit JavaScript zu animieren.
viewTransition.finished
Ein Versprechen, das erfüllt wird, sobald der Endzustand für den Nutzer vollständig sichtbar und interaktiv ist.
Die Ablehnung erfolgt nur, wenn
updateCallback
ein abgelehntes Versprechen zurückgibt, da dies darauf hinweist, dass der Endstatus nicht erstellt wurde.Andernfalls, wenn ein Übergang nicht gestartet wird oder während des Übergangs übersprungen wird, wird der Endstatus trotzdem erreicht, sodass
finished
erfüllt ist.viewTransition.types
Ein
Set
-ähnliches Objekt, das die Typen der aktiven Ansichtsübergänge enthält. Verwenden Sie die Instanzmethodenclear()
,add()
unddelete()
, um die Einträge zu bearbeiten.Wenn Sie in CSS auf einen bestimmten Typ reagieren möchten, verwenden Sie den Pseudoklassen-Selektor
:active-view-transition-type(type)
am Übergangsknoten.Die Typen werden automatisch bereinigt, wenn der Ansichtsübergang abgeschlossen ist.
viewTransition.skipTransition()
Überspringt den Animationsbereich des Übergangs.
Der Aufruf von
updateCallback
wird dadurch nicht übersprungen, da die DOM-Änderung unabhängig von der Überleitung ist.
Referenz zu Standardstil und -übergang
::view-transition
- Das Stamm-Pseudo-Element, das den gesamten Darstellungsbereich füllt und alle
::view-transition-group
enthält. ::view-transition-group
Absolut positioniert.
Übergänge
width
undheight
zwischen den Zuständen „Vorher“ und „Nachher“.Übergänge
transform
zwischen dem „Vorher“- und dem „Nachher“-Viewport-Raum-Quadranten.::view-transition-image-pair
Sie sind absolut in der Lage, die Gruppe zu füllen.
isolation: isolate
muss die Auswirkungen dermix-blend-mode
auf die alte und die neue Ansicht begrenzen.::view-transition-new
und::view-transition-old
Absolut positioniert links oben im Wrapper.
Erfüllt 100% der Gruppenbreite, hat aber eine automatische Höhe. Das Seitenverhältnis wird beibehalten, anstatt die Gruppe zu füllen.
Muss
mix-blend-mode: plus-lighter
enthalten, um einen echten Cross-Fade zu ermöglichen.In der alten Ansicht wird
opacity: 1
durchopacity: 0
ersetzt. In der neuen Ansicht wirdopacity: 0
durchopacity: 1
ersetzt.
Feedback
Wir freuen uns immer über Feedback von Entwicklern. Melden Sie dazu ein Problem mit Vorschlägen und Fragen an die CSS-Arbeitsgruppe auf GitHub. Stellen Sie dem Problem das Präfix [css-view-transitions]
voran.
Sollten Sie auf einen Fehler stoßen, erstellen Sie stattdessen einen Eintrag für das Problem in Chromium.