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

Veröffentlicht: 17. August 2021, Zuletzt aktualisiert: 25. September 2024

Wenn ein Ansichtsübergang in einem einzelnen Dokument ausgeführt wird, wird er als Ansichtsübergang im selben Dokument bezeichnet. Dies ist in der Regel bei Single-Page-Anwendungen (SPAs) der Fall, in denen JavaScript zur Aktualisierung des DOMs verwendet wird. Übergänge zwischen der Ansicht für das gleiche Dokument werden in Chrome ab Chrome 111 unterstützt.

Rufen Sie document.startViewTransition auf, um einen Wechsel der Ansicht für dasselbe Dokument auszulösen:

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 führt es den übergebenen Callback aus, der das DOM aktualisiert und anschließend Snapshots des neuen Status erstellt.

Diese Momentaufnahmen werden dann in einer Baumstruktur aus Pseudoelementen angeordnet und mithilfe von CSS-Animationen animiert. Paare von Momentaufnahmen aus dem alten und neuen Zustand gehen nahtlos von ihrer alten Position und Größe zu ihrem neuen Ort über, während ihr Inhalt überblendet wird. Wenn Sie möchten, können Sie die Animationen mit CSS anpassen.


Standardübergang: Weichzeichner

Der Standardübergang der Ansicht ist ein Überblenden und dient als 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. Das kann so gemacht werden, wie du es möchtest. Sie können beispielsweise Elemente hinzufügen oder entfernen, Klassennamen ändern oder Stile ändern.

Und so funktioniert's:

Der Standard-Crossfade. Minimale Demo Quelle:

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.


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.

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

Nachdem der neue Status erfasst wurde, erstellt 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 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-Inhalt „ersetzt“ gerendert (z. B. <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;
}

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

Langer Überblenden. Minimale Demoversion. Quelle:

Okay, das ist immer noch nicht beeindruckend. Stattdessen wird mit dem folgenden Code der gemeinsame Achsenübergang in 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 hier ist das Ergebnis:

Übergang der gemeinsamen Achse. Minimale Demoversion. Quelle.

Mehrere Elemente umstellen

In der vorherigen Demo ist die gesamte Seite am gemeinsamen Achsenübergang 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, sodass sie separat animiert werden kann. Dazu wird dem Element eine view-transition-name zugewiesen.

.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 während des Übergangs eindeutig identifiziert.

Das Ergebnis:

Übergang der gemeinsam genutzten Achse mit fester Kopfzeile. Minimale Demo Quelle:

Jetzt bleibt die Kopfzeile an ihrer Position und wird überblenden.

Diese CSS-Deklaration hat dazu geführt, dass sich die Pseudoelementstruktur 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 mit CSS ein unabhängiges Targeting sowie verschiedene Übergänge vornehmen. In diesem Fall wurde für main-header jedoch der Standardübergang beibehalten, also ein Cross-Fade.

Okay, der Standardübergang ist nicht nur ein Überblenden, der ::view-transition-group führt auch Übergänge aus:

  • Position und Transformation (mit transform)
  • Breite
  • Höhe

Das spielte bis jetzt keine Rolle, da der Header dieselbe Größe und Position auf beiden Seiten der DOM-Änderung 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 jetzt drei Teile:

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

Aber wieder mit den Standardeinstellungen:

Schiebender Titeltext. Minimale Demo Quelle.

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


Mit view-transition-class mehrere Pseudoelemente auf die gleiche Weise animieren

Unterstützte Browser

  • Chrome: 125.
  • Edge: 125.
  • Firefox: nicht unterstützt
  • Safari-Technologievorschau: unterstützt.

Angenommen, Sie haben einen Übergang mit mehreren Karten, aber auch einem Titel auf der Seite. Um alle Karten mit Ausnahme des Titels zu animieren, müssen Sie einen Selektor 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);
}

Du hast 20 Elemente? Das sind 20 Selektoren, die Sie schreiben müssen. Neues Element hinzufügen? Außerdem müssen Sie die Auswahl, mit der die Animationsstile angewendet werden, vergrößern. Nicht ganz 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).

Aufnahme der Cards-Demo. Wenn du view-transition-class verwendest, wird dasselbe animation-timing-function-Element auf alle Karten angewendet, mit Ausnahme der hinzugefügten oder entfernten Karten.

Übergänge debuggen

Da Ansichtsübergänge auf CSS-Animationen basieren, eignet sich der Bereich Animationen in den Chrome-Entwicklertools hervorragend für das Debugging von Übergängen.

Im Bereich Animationen können Sie die nächste Animation pausieren und dann mit dem Scrubbing-Menü vor- und zurückspringen. Die Übergangs-Pseudoelemente finden Sie im Steuerfeld Elemente.

Ansichtsübergänge mit den Chrome-Entwicklertools debuggen

Elemente mit Wechsel müssen nicht dasselbe DOM-Element sein

Bisher haben wir view-transition-name verwendet, um separate Übergangselemente für den Titel und den Text in der Überschrift zu erstellen. Prinzipiell handelt es sich dabei um dasselbe Element vor und nach der DOM-Änderung. Sie können jedoch Übergänge erstellen, wenn dies 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 es dasselbe view-transition-name erhalten, nur für die Dauer des Übergangs:

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 geht nun zum Hauptbild über. Obwohl es sich um konzeptionell (und buchstäblich) unterschiedliche Elemente handelt, behandelt die Transition API sie als dasselbe, weil 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 im Quellcode.


Benutzerdefinierte Einstiegs- und Exit-Übergänge

Sehen Sie sich dieses Beispiel an:

Seitenleiste öffnen und schließen. Minimale Demo Quelle:

Die Seitenleiste ist Teil der Umstellung:

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

Im Gegensatz zur Kopfzeile 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)

Befindet sich die Seitenleiste jedoch nur auf der neuen Seite, ist das Pseudoelement ::view-transition-old(sidebar) nicht vorhanden. Da es keine „alten“ Bild für die Seitenleiste hat das Bildpaar nur ein ::view-transition-new(sidebar). Befindet sich die Seitenleiste nur auf der alten Seite, hat das Bildpaar nur ein ::view-transition-old(sidebar).

In der vorherigen Demo werden die Übergänge der Seitenleiste unterschiedlich angepasst, je nachdem, ob sie in beiden Zuständen angezeigt wird, sie schließt oder sie verlässt. Sie wird von rechts eingeblendet und verblasst, wenn sie wieder ausgeblendet wird.

Wenn Sie bestimmte Eingangs- und Exit-Übergänge erstellen möchten, können Sie die Pseudoklasse :only-child verwenden, um ein Targeting auf die alten oder neuen Pseudoelemente vorzunehmen, 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-Updates und Warten auf Inhalte

Der an .startViewTransition() übergebene Rückruf kann ein Versprechen 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 das Versprechen erfüllt ist. Während dieser Zeit ist die Seite eingefroren, sodass Verzögerungen hier auf ein Minimum beschränkt werden sollten. Insbesondere sollten Netzwerkabrufe vor dem Aufruf von .startViewTransition() erfolgen, 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, 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 manchen Fällen ist es jedoch besser, die Verzögerung ganz zu vermeiden und den bereits vorhandenen Content zu verwenden.


Bereits vorhandene Inhalte optimal nutzen

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

<ph type="x-smartling-placeholder">
Das Thumbnail, das zu einem größeren Bild übergeht. Demowebsite ansehen

Der Standardübergang ist ein Überblenden. Das bedeutet, dass die Miniaturansicht mit einem noch nicht geladenen Vollbild überblendet werden könnte.

Sie können dies vermeiden, indem Sie 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 verschwindet das Thumbnail nicht, sondern befindet sich nur unter dem vollständigen Bild. Wenn die neue Ansicht also noch nicht geladen wurde, ist das Thumbnail während der gesamten Umstellung sichtbar. Das bedeutet, dass der Übergang sofort beginnen kann und das vollständige Bild geladen werden kann.

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

Änderungen am Seitenverhältnis verarbeiten

Bisher hatten alle Übergänge Elemente mit demselben Seitenverhältnis. Das ist aber nicht immer der Fall. Was ist, wenn das Thumbnail ein Seitenverhältnis von 1:1 und das Hauptbild 16:9 hat?

Ein Element wechselt zu einem anderen, wobei sich das Seitenverhältnis ändert. Minimale Demoversion. Quelle:

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, die Miniaturansicht bleibt in der Mitte des Elements, wenn sich die Breite vergrößert, das vollständige Bild aber nicht zugeschnitten wird. wenn es von 1:1 zu 16:9 wechselt.

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 auf Mobilgeräten und Computern andere Übergänge verwenden, wie in diesem Beispiel, in dem auf Mobilgeräten eine vollständige Folie von der Seite und auf Desktop-Computern eine subtilere Folie zu sehen ist:

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

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

Sie können auch ändern, welche Elemente Sie je nach übereinstimmenden Medienabfragen 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;
  }
}

Eine Präferenz für „verringerte Bewegungen“ bedeutet nicht, dass der Nutzer keine Bewegung haben möchte. Anstelle des vorhergehenden Snippets könnten Sie eine subtilere Animation wählen, die aber dennoch die Beziehung zwischen den Elementen und den Datenfluss zum Ausdruck bringt.


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

Unterstützte Browser

  • Chrome: 125.
  • Edge: 125.
  • Firefox: nicht unterstützt
  • Safari: 18.

Manchmal sollte ein Wechsel von einer bestimmten Ansicht zur anderen genau angepasst werden. 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.

Aufzeichnung der Demo zur Paginierung. Je nachdem, zu welcher Seite Sie wechseln, werden unterschiedliche Übergänge verwendet.

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 Sie beispielsweise bei einer Paginierungssequenz zu einer höheren Seite übergehen, verwenden Sie den Typ forwards und beim Wechsel zu einer niedrigeren Seite 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 den Selektor :active-view-transition-type(), um auf diese Typen zu reagieren. Übergeben Sie den type, auf den Sie ein Targeting vornehmen möchten, in den Selektor. Auf diese Weise können Sie die Stile mehrerer Ansichtsübergänge voneinander getrennt halten, ohne dass die Deklarationen des einen Übergangs die Deklarationen des anderen 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 Demo zur Paginierung wird der Seiteninhalt je nach Seitennummer, die Sie aufrufen, vor- oder zurückgeschoben. Die Typen werden durch einen Klick festgelegt und dann an document.startViewTransition übergeben.

Für das Targeting auf alle Active View-Übergänge unabhängig vom Typ können Sie stattdessen den Pseudoklassenselektor :active-view-transition verwenden.

html:active-view-transition {
    
}

Mehrere Stilübergangsstile für Ansichten mit einem Klassennamen im Stamm des Ansichtsübergangs verarbeiten

Manchmal sollte ein Übergang von einem bestimmten Ansichtstyp zu einem anderen genau angepasst werden. oder „Zurück“ Navigation sollte sich von "forward" unterscheiden. Navigation.

Verschiedene Übergänge beim Zurückgehen. Minimale Demoversion. Quelle:

Vor den Übergangstypen wurde für diese Fälle vorübergehend ein Klassenname im Übergangsstamm festgelegt. Beim Aufrufen von document.startViewTransition ist das <html>-Element der Übergangsknoten, auf den 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');
}

In diesem Beispiel wird transition.finished verwendet, um die Klassen nach Abschluss des Übergangs zu entfernen. Dieses Promise wird aufgelöst, sobald der Übergang den Endzustand erreicht hat. Weitere Eigenschaften dieses Objekts werden in der API-Referenz behandelt.

Jetzt können Sie diesen Klassennamen in Ihrem CSS-Code verwenden, um den Übergang 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 könnte das Vorhandensein dieser Klassen auch verwendet werden, um zu ändern, welche Elemente eine view-transition-name erhalten.


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

Sehen Sie sich diese Demo für eine Videoübergangsposition an:

Videoübergang. Minimale Demoversion. Quelle.

Gab es ein Problem damit? Keine Sorge, wenn Sie das nicht getan haben. Hier ist es verlangsamt:

Videoübergang, langsamer. Minimale Demoversion. Quelle:

Während des Übergangs scheint das Video einzufrieren, 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 Live-Bild der neuen Ansicht ist.

Sie können das Problem beheben, aber fragen Sie sich zuerst, ob sich das lohnt. Wenn Sie „Problem“ nicht gesehen haben wenn der Übergang in normaler Geschwindigkeit abgespielt wird, würde ich ihn nicht ändern.

Wenn du das Problem wirklich beheben möchtest, zeig den ::view-transition-old(video) nicht an. wechseln Sie 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 gesamten Übergangs abgespielt.


Integration in die Navigation API (und andere Frameworks)

Ansichtsübergänge werden so angegeben, dass sie in andere Frameworks oder Bibliotheken integriert werden können. Wenn Ihre Single-Page-Anwendung (SPA) beispielsweise einen Router verwendet, können Sie den Update-Mechanismus des Routers so anpassen, dass der Inhalt mit einem Ansichtsübergang aktualisiert wird.

Im folgenden Code-Snippet aus dieser Paginierungsdemo wurde 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, den Beginn eines Ansichtsübergangs zu verhindern, wenn der Browser einen eigenen visuellen Übergang bereitstellt. Prüfen Sie dazu den Wert des Attributs hasUAVisualTransition der NavigateEvent-Instanz. Das Attribut wird auf true gesetzt, 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. Werden Ansichtenübergänge für dasselbe Dokument nicht unterstützt oder hat der Browser bereits einen eigenen Übergang bereitgestellt, 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. Die Aufnahme links enthält keine Prüfung des Flags hasUAVisualTransition. Die Aufzeichnung auf der rechten Seite enthält die Prüfung. Der manuelle Übergang wird also übersprungen, da der Browser einen visuellen Übergang ermöglicht hat.

Vergleich derselben Website ohne (links) und mit (rechts) einer Prüfung für hasUAVisualTransition

Animationen mit JavaScript

Bisher wurden alle Übergänge mit CSS definiert. Manchmal reicht CSS jedoch nicht aus:

Kreisübergang. Minimale Demo Quelle:

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 zur weitesten Ecke hat. Allerdings wird dies hoffentlich in der Zukunft mit Preisvergleichsportalen 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, eine DOM-Änderung erstellen und einen Übergang dafür erstellen. Der Übergang sollte jedoch als Verbesserung behandelt werden, da Ihre App keinen Fehler eingeben sollte. wenn die DOM-Änderung erfolgreich war, aber der Übergang 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.

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 keinen ready-Status erreichen kann. Das bedeutet jedoch nicht, dass der Ansichtswechsel fehlgeschlagen ist. Das DOM wurde möglicherweise erfolgreich aktualisiert, aber es gab doppelte view-transition-names, sodass die Umstellung übersprungen wurde.

Gehen Sie in diesem Fall so vor:

Do
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 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…

Dies ist keine einfache Funktion für Polyfills. Diese Hilfsfunktion macht die Arbeit in Browsern, die keine Ansichtsübergänge unterstützen, jedoch 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 ist eine Reihe von Beispielen, für die der Helper oben in verschiedenen Frameworks verwendet wird.

  • React (Reagieren): Der Schlüssel ist 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 auch bei der Verwendung der verschiedenen von startViewTransition zurückgegebenen Promis muss darauf geachtet werden, dass der 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 – hier ist das Promise this.updateComplete innerhalb der Komponenten, das erfüllt wird, sobald das DOM aktualisiert wurde.
  • Angular: Der Schlüssel hier ist applicationRef.tick. Damit werden ausstehende DOM-Änderungen geleert. Ab der Angular-Version 17 kannst du withViewTransitions verwenden, die im @angular/router enthalten ist.

API-Referenz

const viewTransition = document.startViewTransition(update)

Neue ViewTransition starten.

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 von updateCallback zurückgegebene Promise abgelehnt wird, wird der Übergang abgebrochen.

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

Neue ViewTransition mit den angegebenen Typen starten

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

Mit types werden die aktiven Typen für den Übergang beim Erfassen oder Ausführen des Übergangs festgelegt. Anfangs ist es leer. Weitere Informationen finden Sie unten unter viewTransition.types.

Instanzmitglieder von ViewTransition:

viewTransition.updateCallbackDone

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

Die View Transition API schließt eine DOM-Änderung ein 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 gleich beginnt.

Wird abgelehnt, wenn der Übergang nicht beginnen kann. Das kann an einer fehlerhaften Konfiguration liegen, z. B. an doppelten view-transition-names, oder wenn updateCallback ein abgelehntes Promise 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.

Es wird nur abgelehnt, wenn updateCallback ein abgelehntes Promise 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 des Active View-Übergangs enthält. Verwenden Sie die Instanzmethoden clear(), add() und delete(), um die Einträge zu bearbeiten.

Um auf einen bestimmten Typ in CSS zu reagieren, verwenden Sie den Pseudoklassenselektor :active-view-transition-type(type) im Übergangsstamm.

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 und height zwischen "Vorher" und „nachher“ Bundesländer.

Übergänge transform zwischen "Vorher" und „nachher“ Darstellungsbereich-Quad.

::view-transition-image-pair

Für diese Gruppe ist absolut bestens gerüstet.

isolation: isolate muss die Auswirkungen der mix-blend-mode auf die alte und die neue Ansicht begrenzen.

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

Absolut positioniert links oben im Wrapper.

Füllt 100% der Gruppenbreite aus, aber die Höhe wird automatisch festgelegt, sodass das Seitenverhältnis beibehalten wird und die Gruppe nicht überfüllt wird.

Muss mix-blend-mode: plus-lighter enthalten, um einen echten Cross-Fade zu ermöglichen.

In der alten Ansicht wird opacity: 1 durch opacity: 0 ersetzt. In der neuen Ansicht wird opacity: 0 durch opacity: 1 ersetzt.


Feedback

Wir freuen uns immer über Feedback von Entwicklern. Melden Sie dazu ein Problem bei der CSS Working Group auf GitHub und fügen Sie Vorschläge und Fragen hinzu. 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.