Übergänge bei der Ansicht desselben Dokuments für Single-Page-Anwendungen

Wenn ein Ansichtsübergang für ein einzelnes Dokument ausgeführt wird, wird dies als Übergang der Ansicht desselben Dokuments bezeichnet. Dies ist normalerweise der Fall bei Single-Page-Anwendungen (SPAs), bei denen JavaScript zum Aktualisieren des DOMs verwendet wird. Übergänge für die Ansicht desselben Dokuments werden ab Chrome 111 in Chrome unterstützt.

Um einen Übergang für die Ansicht desselben Dokuments auszulösen, 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 der Browser aufgerufen wird, erfasst der Browser automatisch Snapshots aller Elemente, für die eine view-transition-name-CSS-Eigenschaft deklariert wurde.

Anschließend wird der übergebene Callback ausgeführt, der das DOM aktualisiert. Anschließend werden Snapshots des neuen Status erstellt.

Diese Snapshots werden dann in einer Baumstruktur aus Pseudoelementen angeordnet und mithilfe von CSS-Animationen animiert. Dabei werden Paare von Momentaufnahmen des alten und des neuen Zustands nahtlos von ihrer alten Position und Größe an den neuen Speicherort verschoben, während die Inhalte überblendet werden. Wenn Sie möchten, können Sie CSS verwenden, um die Animationen anzupassen.


Standardübergang: Überblenden

Der standardmäßige Ansichtsübergang ist eine Überblendung und dient daher als eine gute Einführung in die 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));
}

Dabei ändert updateTheDOMSomehow das DOM in den neuen Status. Sie können dies nach Belieben tun. Beispielsweise können Sie Elemente hinzufügen oder entfernen, Klassennamen oder Stile ändern.

Und schon werden die Seiten verblasst:

Die standardmäßige Überblendung. Minimale Demo. Quelle.

Okay, ein Überblenden ist nicht so beeindruckend. Glücklicherweise können Übergänge angepasst werden. Zuerst müssen Sie jedoch verstehen, wie diese grundlegende Überblendung funktioniert.


So funktionieren diese Umstellungen

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.

Nach Abschluss wird der an .startViewTransition() übergebene Callback aufgerufen. Hier wird das DOM geändert. Anschließend erfasst die API den neuen Zustand der Seite.

Sobald der neue Status erfasst ist, konstruiert die API einen Pseudoelementbaum wie folgt:

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

Das ::view-transition wird in einem Overlay über dem Rest der Seite platziert. Das ist hilfreich, 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) ist eine Live-Darstellung der neuen Ansicht. Beide werden als CSS-„ersetzter Inhalt“ gerendert (wie ein <img>).

Die alte Ansicht wird von opacity: 1 zu opacity: 0 animiert, während die neue Ansicht von opacity: 0 zu opacity: 1 animiert wird, wodurch eine Überblendung entsteht.

Die gesamte Animation wird mithilfe von CSS-Animationen durchgeführt, sodass sie mit CSS angepasst werden können.

Übergang anpassen

Alle Pseudoelemente von Ansichtsübergängen können mit CSS ausgerichtet werden. Da die Animationen mithilfe von CSS definiert werden, können Sie sie mithilfe der vorhandenen CSS-Animationseigenschaften ändern. Beispiel:

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

Mit dieser einen Änderung geht das Ausblenden jetzt sehr langsam:

Lange Überblendung. Minimale Demo. Quelle.

Okay, das ist immer noch nicht beeindruckend. Stattdessen wird mit dem folgenden Code der Übergang der gemeinsamen 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;
}

Und so sieht das Ergebnis aus:

Übergang der gemeinsamen Achse. Minimale Demo. Quelle.

Mehrere Elemente umstellen

In der vorherigen Demo wurde die gesamte Seite am gemeinsamen Achsenübergang beteiligt. Das funktioniert für den Großteil der Seite, aber für die Überschrift scheint es nicht ganz zu stimmen, da sie einfach herausgeschoben wird, um wieder hineinzuschieben.

Um dies zu vermeiden, können Sie den Header aus dem Rest der Seite extrahieren, um ihn separat zu animieren. Dazu wird dem Element ein view-transition-name zugewiesen.

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

Der Wert von view-transition-name kann beliebig sein (mit Ausnahme von none, d. h., es gibt keinen Übergangsnamen). Damit wird das Element während des Übergangs eindeutig identifiziert.

Das Ergebnis:

Gemeinsamer Achsenübergang mit fester Kopfzeile. Minimale Demo. Quelle.

Jetzt bleibt der Header an Ort und Stelle und wird überblendet.

Diese CSS-Deklaration hat die Änderung des Pseudoelement-Baums verursacht:

::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 den Header und eine für den Rest. Sie können mit CSS ein unabhängiges Targeting und mit unterschiedlichen Übergängen festlegen. Allerdings wurde für main-header in diesem Fall der Standardübergang beibehalten, der eine Überblendung darstellt.

Nun, okay, der Standardübergang ist nicht nur ein Überblenden, der ::view-transition-group wechselt auch:

  • Positionieren und transformieren (mit transform)
  • Breite
  • Größe

Das spielt bisher keine Rolle, da der Header dieselbe Größe und Position hat und sich beide Seiten des DOMs ändern. Sie können aber auch den Text in der Kopfzeile extrahieren:

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

fit-content wird verwendet, damit das Element der Größe des Texts entspricht, anstatt es auf die verbleibende Breite zu strecken. Andernfalls verkleinert der Zurückpfeil die Größe des Kopfzeilentextelements, anstatt dieselbe Größe auf beiden Seiten zu sehen.

Jetzt müssen wir mit drei Teilen experimentieren:

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

Aber auch hier gilt wieder, nur die Standardeinstellungen zu verwenden:

Bewegender Text für die Überschrift. Minimale Demo. Quelle.

Jetzt gleitet der Überschriftentext etwas zufriedenstellend über die Seite, um Platz für die Schaltfläche „Zurück“ zu schaffen.


Mit view-transition-class mehrere Pseudoelemente auf dieselbe Weise animieren

Unterstützte Browser

  • 125
  • 125
  • x
  • x

Nehmen wir an, es gibt einen Ansichtsübergang mit mehreren Karten, aber auch mit einem Titel auf der Seite. Wenn Sie alle Karten mit Ausnahme des Titels animieren möchten, müssen Sie einen Selektor schreiben, der auf jede einzelne Infokarte 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);
}

Sie haben 20 Elemente? Das sind 20 Selektoren, die Sie schreiben müssen. Sie fügen ein neues Element hinzu? Dann müssen Sie auch die Auswahl erweitern, mit der die Animationsstile angewendet werden. Nicht gerade skalierbar.

Das view-transition-class kann in den Pseudoelementen des Ansichtsübergangs 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 folgenden Kartenbeispiel wird das vorherige CSS-Snippet verwendet. Auf alle Karten – auch neu hinzugefügte – wird mit einem Selektor dasselbe Timing angewendet: html::view-transition-group(.card).

Aufzeichnung der Kartendemo. Wenn Sie view-transition-class verwenden, wird dieselbe animation-timing-function auf alle Karten mit Ausnahme der hinzugefügten oder entfernten Karten angewendet.

Übergänge zur Fehlerbehebung

Da Ansichtsübergänge auf CSS-Animationen basieren, eignet sich das Steuerfeld Animationen in den Chrome-Entwicklertools hervorragend zum Debuggen von Übergängen.

Im Steuerfeld Animationen können Sie die nächste Animation anhalten und dann mit Scrubbing durch die Animation streichen. Währenddessen finden Sie die Pseudoelemente des Übergangs im Steuerfeld Elemente.

Fehlerbehebung bei Ansichtsumstellungen mit Chrome-Entwicklertools.

Übergehende Elemente müssen nicht dasselbe DOM-Element sein

Bisher haben wir mit view-transition-name separate Übergangselemente für den Header und den Text im Header erstellt. Prinzipiell handelt es sich um dieselben Elemente vor und nach der DOM-Änderung, aber Sie können Übergänge erstellen, wenn dies nicht der Fall ist.

Der Einbettung des Hauptvideos kann beispielsweise ein view-transition-name zugewiesen werden:

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

Wenn dann auf das Thumbnail geklickt wird, kann ihm für die Dauer des Übergangs derselbe view-transition-name zugewiesen werden:

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

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

Und das Ergebnis:

Ein Element geht in ein anderes über. Minimale Demo. Quelle.

Das Thumbnail wechselt jetzt zum Hauptbild. Obwohl sie sich konzeptionell (und buchstäblich) voneinander unterscheiden, behandelt die Transition API sie als identisch, da sie dieselbe view-transition-name haben.

Der echte Code für diesen Übergang ist etwas komplizierter als im vorherigen Beispiel, da er auch den Übergang zurück zur Thumbnail-Seite übernimmt. Die vollständige Implementierung finden Sie in der Quelle.


Benutzerdefinierte Ein- und Exit-Übergänge

Sehen Sie sich dieses Beispiel an:

Seitenleiste aufrufen und schließen. Minimale Demo. Quelle.

Die Seitenleiste ist Teil der Umstellung:

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

Anders als die Kopfzeile im vorherigen Beispiel erscheint die Seitenleiste jedoch nicht auf allen Seiten. Wenn beide Status die Seitenleiste haben, sehen die Pseudoelemente beim Ü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 Pseudoelement ::view-transition-old(sidebar) nicht vorhanden. Da es für die Seitenleiste kein "altes" Bild gibt, hat das Bildpaar nur ein ::view-transition-new(sidebar). Wenn sich die Seitenleiste nur auf der alten Seite befindet, hat das Bildpaar dagegen nur ein ::view-transition-old(sidebar).

In der vorherigen Demo ändert sich der Übergang der Seitenleiste, je nachdem, ob sie in beiden Status aufgerufen, geschlossen oder vorhanden ist. Er schiebt nach rechts und wird wieder eingeblendet, indem er nach rechts geschoben und dann wieder ausgeblendet wird. Wenn er sich in beiden Bundesstaaten befindet, bleibt er an Ort und Stelle.

Wenn Sie bestimmte Ein- und Ausstiegsübergänge erstellen möchten, können Sie die Pseudoklasse :only-child für das Targeting der alten oder neuen Pseudoelemente verwenden, 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 für den Fall, dass die Seitenleiste in beiden Status vorhanden ist, da die Standardeinstellung 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 bereit sind.

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

Die Umstellung wird erst gestartet, wenn das Versprechen erfüllt ist. Während dieser Zeit ist die Seite eingefroren, sodass die Verzögerungen hier auf ein Minimum reduziert werden sollten. Insbesondere sollten Netzwerkabrufe vor dem Aufruf von .startViewTransition() durchgeführt werden, während die Seite noch vollständig interaktiv ist, und nicht als Teil des .startViewTransition()-Callbacks.

Wenn Sie warten möchten, bis Bilder oder Schriftarten bereit sind, verwenden Sie ein aggressives Zeitlimit:

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 manchen Fällen ist es jedoch besser, die Verzögerung vollständig zu vermeiden und die bereits vorhandenen Inhalte zu verwenden.


Vorhandene Inhalte optimal nutzen

Wenn die Miniaturansicht zu einem größeren Bild übergeht:

Die Miniaturansicht, die zu einem größeren Bild übergeht. Demowebsite ansehen

Die Standardeinstellung ist „Überblenden“. Das bedeutet, dass das Thumbnail möglicherweise mit einem noch nicht geladenen vollständigen Bild überblendet.

Eine Möglichkeit besteht darin, mit dem Übergang zu warten, bis das vollständige Bild geladen ist. Idealerweise sollte dies vor dem Aufruf von .startViewTransition() erfolgen, damit die Seite interaktiv bleibt und ein rotierendes Ladesymbol eingeblendet wird, um den Nutzer darüber zu informieren, dass Inhalte geladen werden. Aber in diesem Fall gibt es eine bessere Methode:

::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;
}

Das Thumbnail verschwindet jetzt nicht, sondern befindet sich einfach unter dem Vollbild. Wenn also die neue Ansicht nicht geladen wurde, ist das Thumbnail während des Übergangs sichtbar. Der Übergang kann also sofort beginnen und das vollständige Bild kann in einem eigenen Tempo geladen werden.

Dies würde nicht funktionieren, wenn die neue Ansicht Transparenz aufweisen würde, aber in diesem Fall wissen wir, dass das nicht der Fall ist, sodass wir diese Optimierung vornehmen können.

Mit Änderungen des Seitenverhältnisses umgehen

Bisher erfolgten alle Übergänge auf Elemente mit demselben Seitenverhältnis. Das ist jedoch nicht immer der Fall. Was ist, wenn das Thumbnail 1:1 und das Hauptbild 16:9 ist?

Ein Element geht zum anderen über, wobei sich das Seitenverhältnis ändert. Minimale Demo. Quelle.

Beim Standardübergang wird die Gruppe von der Vorher- zur Nachher-Größe animiert. Die alte und die neue Ansicht haben eine Breite von 100% mit der automatischen Höhe. Das bedeutet, dass ihr Seitenverhältnis unabhängig von der Größe der Gruppe beibehalten wird.

Dies ist eine gute Standardeinstellung, aber in diesem Fall ist es nicht gewollt. 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 die Miniaturansicht bei Vergrößerung der Breite in der Mitte des Elements bleibt, das gesamte Bild jedoch beim Übergang von 1:1 zu 16:9 nicht zugeschnitten wird.

Weitere Informationen findest du unter (Übergänge ansehen: Änderungen des Seitenverhältnisses handhaben)(https://jakearchibald.com/2024/view-transitions-handling-aspect-ratio-changes/)


Mit Medienabfragen die Übergänge für verschiedene Gerätestatus ändern

Sie können auf Mobilgeräten unterschiedliche Übergänge im Vergleich zu Desktop-Computern verwenden. Hier ein Beispiel, bei dem auf Mobilgeräten eine vollständige Folie von der Seite gezeigt wird, auf Desktop jedoch eine subtilere Folie:

Ein Element geht in ein anderes über. Minimale Demo. Quelle.

Dies kann mit normalen Medienabfragen erreicht werden:

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

Abhängig von den passenden Medienabfragen können Sie auch ändern, welchen Elementen Sie eine view-transition-name zuweisen.


Auf die Einstellung für weniger Bewegung reagieren

Nutzer können angeben, dass sie weniger Bewegung in ihrem Betriebssystem bevorzugen. Diese Präferenz wird in CSS angezeigt.

Sie können die Umstellung für folgende Nutzer verhindern:

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

Die Einstellung für „Reduzierte Bewegung“ bedeutet jedoch nicht, dass der Nutzer keine Bewegungen wünscht. Anstelle des vorherigen Ausschnitts können Sie auch eine subtilere Animation wählen, die aber dennoch die Beziehung zwischen den Elementen und dem Datenfluss ausdrückt.


Mehrere Stile für Ansichtsübergänge mit Ansichtsübergangstypen verarbeiten

Manchmal sollte ein Übergang von einer bestimmten Ansicht zu einer anderen einen speziell angepassten Übergang haben. Wenn Sie beispielsweise in einer Paginierungssequenz zur nächsten oder zur vorherigen Seite wechseln, können Sie den Inhalt in eine andere Richtung verschieben, je nachdem, ob Sie eine höhere oder eine niedrigere Seite aus der Abfolge aufrufen.

Aufzeichnung der Pagination-Demo. Sie verwendet unterschiedliche Übergänge, je nachdem, welche Seite Sie aufrufen.

Dazu können Sie Aufrufübergangstypen verwenden, mit denen Sie einem Active View-Übergang einen oder mehrere Typen zuweisen können. Wenn Sie beispielsweise in einer Paginierungssequenz zu einer höheren Seite wechseln, verwenden Sie den Typ forwards und beim Wechseln zu einer niedrigeren Seite den Typ backwards. Diese Typen sind nur beim Erfassen oder Ausführen eines Übergangs aktiv und jeder Typ kann über CSS angepasst werden, um unterschiedliche Animationen zu verwenden.

Um Typen bei einem Wechsel der Ansicht für dasselbe Dokument zu verwenden, übergeben Sie types an die Methode startViewTransition. Um dies zuzulassen, akzeptiert document.startViewTransition auch ein Objekt: update ist die Callback-Funktion, die das DOM aktualisiert, und types ist ein Array mit den verschiedenen Typen.

const direction = determineBackwardsOrForwards();

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

Verwenden Sie die :active-view-transition-type()-Auswahl, um auf diese Typen zu reagieren. Übergeben Sie den type, auf den Sie ein Targeting vornehmen möchten, an die Auswahl. So können Sie die Stile mehrerer Ansichtsübergänge voneinander getrennt halten, ohne dass die Deklarationen der einen Ansicht die Deklarationen der anderen beeinträchtigen.

Da Typen nur beim Erfassen oder Ausführen des Übergangs angewendet werden, können Sie mit der Auswahl eine 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 Demo zur Paginierung blättern die Seiteninhalte je nach Seitennummer vor oder zurück. Die Typen werden durch einen Klick bestimmt, bei dem sie an document.startViewTransition übergeben werden.

Wenn Sie unabhängig vom Typ ein Targeting auf einen beliebigen Active View-Übergang vornehmen möchten, können Sie stattdessen den Pseudoklassenselektor :active-view-transition verwenden.

html:active-view-transition {
    …
}

Mehrere Stile für Ansichtsübergänge mit einem Klassennamen im Stamm der Ansichtsübergang verarbeiten

Manchmal sollte ein Übergang von einer bestimmten Art zu einer anderen einen speziell angepassten Übergang aufweisen. Alternativ sollte sich eine Navigation "Zurück" von einer Vorwärtsnavigation unterscheiden.

Andere Übergänge beim Zurückkehren. Minimale Demo. Quelle.

Vor Übergangstypen bestand die Lösung für diese Fälle darin, vorübergehend einen Klassennamen im Übergangsstamm festzulegen. Beim Aufrufen von document.startViewTransition ist dieser Übergangsstamm das <html>-Element, auf das über document.documentElement in JavaScript 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');
}

Zum Entfernen der Klassen nach Abschluss des Übergangs wird in diesem Beispiel transition.finished verwendet, ein Promise, das aufgelöst wird, sobald der Übergang seinen Endzustand erreicht hat. Weitere Eigenschaften dieses Objekts finden Sie in der API-Referenz.

Jetzt können Sie diesen Klassennamen in Ihrem CSS-Code verwenden, um die Umstellung 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;
}

Wie bei Medienabfragen kann das Vorhandensein dieser Klassen auch dazu verwendet werden, zu ändern, welche Elemente ein view-transition-name erhalten.


Übergänge ausführen, ohne andere Animationen einzufrieren

Sehen Sie sich diese Demo einer Videoübergangsposition an:

Videoübergang. Minimale Demo. Quelle.

Ist dir etwas ausgesprochen? Keine Sorge, wenn nicht. Hier wird es verlangsamt:

Videoübergang, langsamer. Minimale Demo. Quelle.

Während des Übergangs erscheint das Video scheinbar eingefroren. Anschließend wird die wiedergegebene Version eingeblendet. Das liegt daran, dass ::view-transition-old(video) ein Screenshot der alten Ansicht ist, während ::view-transition-new(video) ein Live-Bild der neuen Ansicht ist.

Du kannst dieses Problem beheben, aber frage dich zuerst, ob es sich lohnt, es zu beheben. Sollte das "Problem" bei der Wiedergabe mit normaler Geschwindigkeit nicht auftreten, würde ich es nicht ändern.

Wenn Sie das Problem wirklich beheben möchten, sollten Sie die ::view-transition-old(video) nicht einblenden. Wechseln Sie stattdessen direkt zu ::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.

Videoübergang, langsamer. Minimale Demo. Quelle.

Jetzt wird das Video während des Übergangs abgespielt.


Animationen mit JavaScript

Bisher wurden alle Übergänge mit CSS definiert, aber manchmal ist CSS nicht ausreichend:

Kreisübergang. Minimale Demo. Quelle.

Einige Teile dieser Umstellung sind mit CSS allein nicht möglich:

  • Die Animation beginnt an der Klickposition.
  • Die Animation endet mit dem Kreis mit einem Radius zur entferntesten Ecke. Wir hoffen, dass dies in Zukunft mit Preisvergleichsportalen möglich sein wird.

Übergänge können Sie glücklicherweise mit der Web Animation API 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 Promise, das aufgelöst wird, nachdem die Pseudoelemente des Übergangs erstellt wurden. Weitere Eigenschaften dieses Objekts finden Sie in der API-Referenz.


Übergänge als Verbesserung

Die View Transition API wurde entwickelt, um eine DOM-Änderung zu „umfassen“ und einen Übergang dafür zu erstellen. Der Übergang sollte jedoch als Verbesserung behandelt werden, da die Anwendung nicht in den Status „Fehler“ wechseln sollte, wenn die DOM-Änderung erfolgreich ist, die Umstellung jedoch fehlschlägt. Im Idealfall sollte die Umstellung nicht fehlschlagen. Falls doch, sollte der Rest der Nutzererfahrung nicht beeinträchtigt werden.

Übergänge werden als Verbesserung behandelt. Verwende daher keine Übergangsversprechen, die dazu führen könnten, dass deine App ausgelöst wird, wenn der Übergang fehlschlägt.

Don'ts
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 aber nicht, dass die Ansicht nicht gewechselt werden konnte. Das DOM wurde möglicherweise aktualisiert, aber es gab doppelte view-transition-names, sodass der Übergang übersprungen wurde.

Gehen Sie in diesem Fall so vor:

Das sollten Sie tun:
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 abzulehnen, wenn sie fehlschlägt. switchView lehnt den Wechsel nicht mehr ab, wenn der Übergang fehlschlägt, wird er aufgelöst, wenn die DOM-Aktualisierung abgeschlossen ist, und lehnt ihn ab, wenn er fehlschlägt.

Soll switchView aufgelöst werden, sobald die neue Ansicht beglichen ist (z. B. wenn ein animierter Übergang abgeschlossen oder bis zum Ende übersprungen wurde), ersetzen Sie transition.updateCallbackDone durch transition.finished.


Kein Polyfill, aber...

Das ist keine einfache Funktion für Polyfill. Diese Hilfsfunktion erleichtert jedoch die Arbeit in Browsern, die keine Ansichtsübergänge nicht 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');
  }
}

Und es kann so 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 trotzdem aufgerufen, es wird aber kein animierter Übergang durchgeführt.

Sie können auch einige classNames angeben, die während der Umstellung zu <html> hinzugefügt werden sollen. So lassen sich den Übergang je nach Art der Navigation leichter ändern.

Wenn keine Animation angezeigt werden soll, kannst du auch true an skipTransition übergeben. Das funktioniert sogar in Browsern, die Ansichtsübergänge unterstützen. Das ist nützlich, wenn Nutzer für Ihre Website die Umstellung deaktivieren möchten.


Mit Frameworks arbeiten

Wenn Sie mit einer Bibliothek oder einem Framework arbeiten, das DOM-Änderungen abstrahiert, ist es schwierig zu wissen, wann die DOM-Änderung abgeschlossen ist. Im Folgenden finden Sie eine Reihe von Beispielen, die anhand des oben beschriebenen Helpers in verschiedenen Frameworks verwendet werden.

  • Reagieren: Der Schlüssel ist hier flushSync, mit dem eine Reihe von Statusänderungen synchron angewendet wird. Ja, es gibt eine große Warnung bezüglich der Verwendung dieser API, aber Dan Abramov versichert mir, dass dies in diesem Fall angemessen ist. Wie bei React und asynchronem Code solltest du bei Verwendung der verschiedenen Promise, die von startViewTransition zurückgegeben werden, darauf achten, dass dein Code mit dem richtigen Status ausgeführt wird.
  • Vue.js: Der Schlüssel ist hier nextTick. Er wird ausgeführt, sobald das DOM aktualisiert wurde.
  • Svelte – sehr ähnlich wie Vue, aber die Methode, mit der die nächste Änderung erwartet wird, ist tick.
  • Lit: Das Entscheidende ist hier das Versprechen this.updateComplete in den Komponenten, das erfüllt wird, sobald das DOM aktualisiert wurde.
  • Angular: Der Schlüssel ist hier applicationRef.tick. Damit werden ausstehende DOM-Änderungen gelöscht. Ab Angular Version 17 können Sie withViewTransitions verwenden, die in @angular/router enthalten ist.

API-Referenz

const viewTransition = document.startViewTransition(update)

Starte einen neuen ViewTransition.

update ist eine Funktion, die aufgerufen wird, sobald der aktuelle Status des Dokuments erfasst ist.

Wenn dann das von updateCallback zurückgegebene Versprechen erfüllt, beginnt der Übergang im nächsten Frame. Wenn das von updateCallback zurückgegebene Promise abgelehnt wird, wird die Umstellung abgebrochen.

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

Einen neuen ViewTransition mit den angegebenen Typen starten

update wird aufgerufen, sobald der aktuelle Status des Dokuments erfasst wurde.

types legt die aktiven Typen für den Übergang fest, wenn der Übergang erfasst oder ausgeführt wird. Sie ist anfangs leer. Weitere Informationen finden Sie unter viewTransition.types weiter unten.

Instanzmitglieder von ViewTransition:

viewTransition.updateCallbackDone

Ein Versprechen, das erfüllt wird, wenn das von updateCallback zurückgegebene Versprechen erfüllt bzw. abgelehnt wird.

Die View Transition API umschließt eine DOM-Änderung und erstellt einen Übergang. Manchmal ist Ihnen jedoch der Erfolg oder Misserfolg der Übergangsanimation nicht wichtig, Sie möchten nur wissen, ob und wann die DOM-Änderung erfolgt. updateCallbackDone ist für diesen Anwendungsfall gedacht.

viewTransition.ready

Ein Versprechen, das erfüllt wird, sobald die Pseudoelemente für den Übergang erstellt sind und die Animation gleich beginnt.

Wird abgelehnt, wenn die Umstellung nicht beginnen kann. Das kann an einer fehlerhaften Konfiguration liegen, z. B. doppelte view-transition-names oder wenn updateCallback ein abgelehntes Promise zurückgibt.

Dies ist nützlich, wenn Sie die Übergangs-Pseudoelemente mit JavaScript animieren möchten.

viewTransition.finished

Ein Versprechen, das erfüllt wird, sobald der Endzustand für den Nutzer vollständig sichtbar und interaktiv ist.

Es wird nur abgelehnt, wenn updateCallback ein abgelehntes Promise zurückgibt, was darauf hinweist, dass der Endstatus nicht erstellt wurde.

Wenn ein Übergang nicht beginnt oder während des Übergangs übersprungen wird, ist der Endstatus dennoch erreicht, sodass finished erfüllt ist.

viewTransition.types

Ein Set-ähnliches Objekt, das die Typen des Active View-Übergangs enthält. Verwenden Sie zum Bearbeiten der Einträge die Instanzmethoden clear(), add() und delete().

Wenn Sie auf einen bestimmten CSS-Typ reagieren möchten, verwenden Sie den Pseudoklassenselektor :active-view-transition-type(type) im Übergangsstamm.

Typen werden automatisch bereinigt, wenn der Ansichtsübergang abgeschlossen ist.

viewTransition.skipTransition()

Die Animation des Übergangs überspringen.

Dadurch wird der Aufruf von updateCallback nicht übersprungen, da die DOM-Änderung von der Umstellung getrennt ist.


Standardstil und -übergangsreferenz

::view-transition
Das Pseudoelement, das den Darstellungsbereich ausfüllt und jedes ::view-transition-group-Element enthält.
::view-transition-group

Perfekte Position.

Übergänge width und height zwischen den Status „Vorher“ und „Nachher“.

Übergänge transform zwischen dem „Vorher“- und „Nachher“-Quadrat im Darstellungsbereich.

::view-transition-image-pair

Absolut positioniert, um die Gruppe zu füllen.

Hat isolation: isolate, um die Auswirkungen von mix-blend-mode auf die alte und die neue Ansicht einzuschränken.

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

Sie ist absolut links oben im Wrapper positioniert.

Füllt 100% der Gruppenbreite aus, hat aber eine automatische Höhe. Dadurch wird das Seitenverhältnis beibehalten, die Gruppe wird also nicht ausgefüllt.

Verfügt über mix-blend-mode: plus-lighter, um ein echtes Überblenden zu ermöglichen.

Die alte Ansicht wechselt von opacity: 1 zu opacity: 0. Die neue Ansicht wechselt von opacity: 0 zu opacity: 1.


Feedback

Wir freuen uns immer über Feedback von Entwicklern. Melden Sie dazu bei der CSS-Arbeitsgruppe auf GitHub ein Problem mit Vorschlägen und Fragen. Stellen Sie dem Problem „[css-view-transitions]“ als Präfix vor.

Falls ein Fehler auftritt, melde den Fehler in Chromium.