Dokumentübergreifende Ansichtsübergänge für mehrseitige Anwendungen

Wenn ein Ansichtsübergang zwischen zwei verschiedenen Dokumenten erfolgt, wird dies als dokumentübergreifender Ansichtsübergang bezeichnet. Das ist in der Regel bei mehrseitigen Anwendungen (Multi-Page Applications, MPA) der Fall. In Chrome 126 und höher werden plattformübergreifende Ansichtsübergänge unterstützt.

Unterstützte Browser

  • Chrome: 126.
  • Edge: 126.
  • Firefox: Nicht unterstützt.
  • Safari Technology Preview: unterstützt

Ansichtsübergänge zwischen Dokumenten basieren auf denselben Bausteinen und Prinzipien wie Ansichtsübergänge innerhalb eines Dokuments. Das ist ganz bewusst so:

  1. Der Browser erstellt Snapshots von Elementen, die sowohl auf der alten als auch auf der neuen Seite ein eindeutiges view-transition-name haben.
  2. Das DOM wird aktualisiert, während das Rendering unterdrückt wird.
  3. Die Übergänge werden schließlich durch CSS-Animationen unterstützt.

Im Vergleich zu Ansichtsübergängen innerhalb desselben Dokuments müssen Sie bei dokumentübergreifenden Ansichtsübergängen nicht document.startViewTransition aufrufen, um einen Ansichtsübergang zu starten. Stattdessen ist der Auslöser für einen dokumentübergreifenden Ansichtsübergang eine Navigation innerhalb desselben Ursprungs von einer Seite zur anderen. Diese Aktion wird in der Regel durch einen Klick des Nutzers auf Ihrer Website ausgeführt.

Mit anderen Worten: Es gibt keine API, die aufgerufen werden kann, um einen Ansichtsübergang zwischen zwei Dokumenten zu starten. Es müssen jedoch zwei Bedingungen erfüllt sein:

  • Beide Dokumente müssen am selben Ursprung vorhanden sein.
  • Auf beiden Seiten muss die Funktion aktiviert sein, damit der Wechsel der Ansicht möglich ist.

Beide Bedingungen werden weiter unten in diesem Dokument erläutert.


Ansichtsübergänge zwischen Dokumenten sind auf Navigationen mit demselben Ursprung beschränkt

Ansichtsübergänge zwischen Dokumenten sind nur auf Navigationen innerhalb desselben Ursprungs beschränkt. Eine Navigation gilt als Navigation mit demselben Ursprung, wenn der Ursprung beider teilnehmender Seiten identisch ist.

Der Ursprung einer Seite ist eine Kombination aus dem verwendeten Schema, dem Hostnamen und dem Port, wie auf web.dev beschrieben.

Beispiel-URL mit hervorgehobenem Schema, Hostnamen und Port Zusammen bilden sie den Ursprung.
Beispiel-URL mit hervorgehobenem Schema, Hostnamen und Port. Zusammen bilden sie den Ursprung.

Beispielsweise kann beim Wechseln von developer.chrome.com zu developer.chrome.com/blog eine dokumentübergreifende Ansichtsübergang erfolgen, da diese Seiten dieselbe Quelle haben. Dieser Übergang ist nicht möglich, wenn Sie von developer.chrome.com zu www.chrome.com wechseln, da es sich um unterschiedliche Ursprünge und dieselbe Website handelt.


Ansichtsübergänge zwischen Dokumenten müssen aktiviert werden

Damit ein dokumentübergreifender Wechsel zwischen zwei Dokumenten möglich ist, müssen beide beteiligten Seiten dies zulassen. Dazu wird in CSS die At-rule @view-transition verwendet.

Legen Sie im Attribut-Regeln-Element @view-transition den navigation-Beschreibungstext auf auto fest, um Ansichtsübergänge für dokumentübergreifende Navigationen mit demselben Ursprung zu aktivieren.

@view-transition {
  navigation: auto;
}

Wenn du den navigation-Beschreibungstext auf auto festlegst, erlaubst du Ansichtsübergänge für die folgenden NavigationType:

  • traverse
  • push oder replace, wenn die Aktivierung nicht vom Nutzer über die Browser-Benutzeroberfläche initiiert wurde.

Von auto ausgeschlossen sind beispielsweise Navigationen über die URL-Adressleiste oder Klicks auf Lesezeichen sowie jede Art von vom Nutzer oder Script initiiertem Neuladen.

Wenn eine Navigation zu lange dauert (in Chrome mehr als vier Sekunden), wird der Ansichtsübergang mit einem TimeoutError DOMException übersprungen.

Demo für bereichsübergreifende Ansichtsübergänge

In der folgenden Demo werden Ansichtsübergänge verwendet, um eine Demo für den Stack Navigator zu erstellen. Hier werden keine Aufrufe von document.startViewTransition() ausgeführt. Die Ansichtsübergänge werden durch das Wechseln von einer Seite zur anderen ausgelöst.

Aufzeichnung der Demo für den Stack Navigator. Erfordert Chrome 126 oder höher.

Ansichtsübergänge zwischen Dokumenten anpassen

Es gibt einige Funktionen der Webplattform, mit denen Sie Ansichtsübergänge zwischen Dokumenten anpassen können.

Diese Funktionen sind nicht Teil der View Transition API-Spezifikation, sondern wurden für die Verwendung in Verbindung mit dieser entwickelt.

Die Ereignisse pageswap und pagereveal

Unterstützte Browser

  • Chrome: 124.
  • Edge: 124.
  • Firefox: Nicht unterstützt.
  • Safari: Nicht unterstützt.

Quelle

Damit Sie Ansichtsübergänge zwischen Dokumenten anpassen können, enthält die HTML-Spezifikation zwei neue Ereignisse, die Sie verwenden können: pageswap und pagereveal.

Diese beiden Ereignisse werden bei jeder Navigation zwischen Dokumenten mit demselben Ursprung ausgelöst, unabhängig davon, ob eine Ansichtsübergang bevorsteht oder nicht. Wenn zwischen den beiden Seiten ein Seitenübergang bevorsteht, können Sie über die Property viewTransition auf das ViewTransition-Objekt zugreifen.

  • Das Ereignis pageswap wird ausgelöst, bevor der letzte Frame einer Seite gerendert wird. So können Sie kurz vor dem Erstellen der alten Snapshots noch Last-Minute-Änderungen an der ausgehenden Seite vornehmen.
  • Das Ereignis pagereveal wird auf einer Seite ausgelöst, nachdem sie initialisiert oder reaktiviert wurde, aber noch vor der ersten Rendering-Möglichkeit. So können Sie die neue Seite anpassen, bevor die neuen Snapshots erstellt werden.

Mit diesen Ereignissen können Sie beispielsweise einige view-transition-name-Werte schnell festlegen oder ändern oder Daten von einem Dokument an ein anderes übergeben, indem Sie Daten in sessionStorage schreiben und lesen, um den Ansichtsübergang vor der Ausführung anzupassen.

let lastClickX, lastClickY;
document.addEventListener('click', (event) => {
  if (event.target.tagName.toLowerCase() === 'a') return;
  lastClickX = event.clientX;
  lastClickY = event.clientY;
});

// Write position to storage on old page
window.addEventListener('pageswap', (event) => {
  if (event.viewTransition && lastClick) {
    sessionStorage.setItem('lastClickX', lastClickX);
    sessionStorage.setItem('lastClickY', lastClickY);
  }
});

// Read position from storage on new page
window.addEventListener('pagereveal', (event) => {
  if (event.viewTransition) {
    lastClickX = sessionStorage.getItem('lastClickX');
    lastClickY = sessionStorage.getItem('lastClickY');
  }
});

Sie können den Übergang in beiden Ereignissen überspringen.

window.addEventListener("pagereveal", async (e) => {
  if (e.viewTransition) {
    if (goodReasonToSkipTheViewTransition()) {
      e.viewTransition.skipTransition();
    }
  }
}

Das ViewTransition-Objekt in pageswap und pagereveal sind zwei verschiedene Objekte. Außerdem werden die verschiedenen Versprechen unterschiedlich behandelt:

  • pageswap: Sobald das Dokument ausgeblendet ist, wird das alte ViewTransition-Objekt übersprungen. In diesem Fall lehnt viewTransition.ready ab und viewTransition.finished löst das Problem.
  • pagereveal: Das updateCallBack-Versprechen ist an dieser Stelle bereits erfüllt. Sie können die Versprechen viewTransition.ready und viewTransition.finished verwenden.

Unterstützte Browser

  • Chrome: 123.
  • Edge: 123.
  • Firefox: Nicht unterstützt.
  • Safari: Nicht unterstützt.

Quelle

Sowohl bei pageswap- als auch bei pagereveal-Ereignissen können Sie Aktionen basierend auf den URLs der alten und neuen Seiten ausführen.

Im MPA Stack Navigator hängt die Art der Animation beispielsweise vom Navigationspfad ab:

  • Wenn du von der Übersichtsseite zu einer Detailseite wechselst, müssen die neuen Inhalte von rechts nach links eingeblendet werden.
  • Wenn Sie von der Detailseite zur Übersichtsseite wechseln, müssen die alten Inhalte von links nach rechts herausgeschoben werden.

Dazu benötigen Sie Informationen zur Navigation, die im Fall von pageswap kurz bevorsteht oder im Fall von pagereveal gerade stattgefunden hat.

Dazu können Browser jetzt NavigationActivation-Objekte bereitstellen, die Informationen zur Navigation innerhalb desselben Ursprungs enthalten. Dieses Objekt gibt den verwendeten Navigationstyp, den aktuellen und den letzten Zielverlaufseintrag aus navigation.entries() aus der Navigation API an.

Auf einer aktivierten Seite können Sie über navigation.activation auf dieses Objekt zugreifen. Im Ereignis pageswap können Sie über e.activation darauf zugreifen.

In dieser Demo für Profile werden NavigationActivation-Informationen in den Ereignissen pageswap und pagereveal verwendet, um die view-transition-name-Werte für die Elemente festzulegen, die an der Ansichtsübergang teilnehmen müssen.

So müssen Sie nicht jedes Element in der Liste vorab mit einem view-transition-name ausstatten. Stattdessen geschieht dies Just-in-Time mit JavaScript und nur bei Elementen, die es benötigen.

Aufzeichnung der Demo für Profile. Erfordert Chrome 126 oder höher.

Der Code sieht so aus:

// OLD PAGE LOGIC
window.addEventListener('pageswap', async (e) => {
  if (e.viewTransition) {
    const targetUrl = new URL(e.activation.entry.url);

    // Navigating to a profile page
    if (isProfilePage(targetUrl)) {
      const profile = extractProfileNameFromUrl(targetUrl);

      // Set view-transition-name values on the clicked row
      document.querySelector(`#${profile} span`).style.viewTransitionName = 'name';
      document.querySelector(`#${profile} img`).style.viewTransitionName = 'avatar';

      // Remove view-transition-names after snapshots have been taken
      // (this to deal with BFCache)
      await e.viewTransition.finished;
      document.querySelector(`#${profile} span`).style.viewTransitionName = 'none';
      document.querySelector(`#${profile} img`).style.viewTransitionName = 'none';
    }
  }
});

// NEW PAGE LOGIC
window.addEventListener('pagereveal', async (e) => {
  if (e.viewTransition) {
    const fromURL = new URL(navigation.activation.from.url);
    const currentURL = new URL(navigation.activation.entry.url);

    // Navigating from a profile page back to the homepage
    if (isProfilePage(fromURL) && isHomePage(currentURL)) {
      const profile = extractProfileNameFromUrl(currentURL);

      // Set view-transition-name values on the elements in the list
      document.querySelector(`#${profile} span`).style.viewTransitionName = 'name';
      document.querySelector(`#${profile} img`).style.viewTransitionName = 'avatar';

      // Remove names after snapshots have been taken
      // so that we're ready for the next navigation
      await e.viewTransition.ready;
      document.querySelector(`#${profile} span`).style.viewTransitionName = 'none';
      document.querySelector(`#${profile} img`).style.viewTransitionName = 'none';
    }
  }
});

Der Code beseitigt auch die Spuren seiner Ausführung, indem die view-transition-name-Werte nach dem Ausführen der Ansichtsübergang entfernt werden. So ist die Seite für nachfolgende Navigationen bereit und kann auch den Navigationsverlauf verarbeiten.

Verwenden Sie dazu diese Dienstprogrammfunktion, mit der view-transition-names vorübergehend festgelegt werden.

const setTemporaryViewTransitionNames = async (entries, vtPromise) => {
  for (const [$el, name] of entries) {
    $el.style.viewTransitionName = name;
  }

  await vtPromise;

  for (const [$el, name] of entries) {
    $el.style.viewTransitionName = '';
  }
}

Der vorherige Code kann jetzt so vereinfacht werden:

// OLD PAGE LOGIC
window.addEventListener('pageswap', async (e) => {
  if (e.viewTransition) {
    const targetUrl = new URL(e.activation.entry.url);

    // Navigating to a profile page
    if (isProfilePage(targetUrl)) {
      const profile = extractProfileNameFromUrl(targetUrl);

      // Set view-transition-name values on the clicked row
      // Clean up after the page got replaced
      setTemporaryViewTransitionNames([
        [document.querySelector(`#${profile} span`), 'name'],
        [document.querySelector(`#${profile} img`), 'avatar'],
      ], e.viewTransition.finished);
    }
  }
});

// NEW PAGE LOGIC
window.addEventListener('pagereveal', async (e) => {
  if (e.viewTransition) {
    const fromURL = new URL(navigation.activation.from.url);
    const currentURL = new URL(navigation.activation.entry.url);

    // Navigating from a profile page back to the homepage
    if (isProfilePage(fromURL) && isHomePage(currentURL)) {
      const profile = extractProfileNameFromUrl(currentURL);

      // Set view-transition-name values on the elements in the list
      // Clean up after the snapshots have been taken
      setTemporaryViewTransitionNames([
        [document.querySelector(`#${profile} span`), 'name'],
        [document.querySelector(`#${profile} img`), 'avatar'],
      ], e.viewTransition.ready);
    }
  }
});

Warten auf das Laden von Inhalten mit Rendering-Blockierung

Unterstützte Browser

  • Chrome: 124.
  • Edge: 124.
  • Firefox: Nicht unterstützt.
  • Safari: Nicht unterstützt.

In einigen Fällen möchten Sie das erste Rendern einer Seite verzögern, bis ein bestimmtes Element im neuen DOM vorhanden ist. So wird ein Flackern vermieden und der Zustand, zu dem die Animation erfolgt, ist stabil.

Definieren Sie in der <head> mithilfe des folgenden Meta-Tags eine oder mehrere Element-IDs, die vorhanden sein müssen, bevor die Seite zum ersten Mal gerendert wird.

<link rel="expect" blocking="render" href="#section1">

Dieses Meta-Tag bedeutet, dass das Element im DOM vorhanden sein sollte, nicht, dass der Inhalt geladen werden sollte. Bei Bildern reicht beispielsweise das Vorhandensein des <img>-Tags mit dem angegebenen id im DOM-Baum aus, damit die Bedingung als wahr ausgewertet wird. Das Bild selbst wird möglicherweise noch geladen.

Bevor Sie das Rendern blockieren, sollten Sie sich darüber im Klaren sein, dass das inkrementelle Rendern ein grundlegender Aspekt des Webs ist. Seien Sie also vorsichtig, wenn Sie das Rendern blockieren. Die Auswirkungen des Blockierens des Renderings müssen von Fall zu Fall bewertet werden. Verwenden Sie blocking=render standardmäßig nur, wenn Sie die Auswirkungen auf Ihre Nutzer aktiv messen und bewerten können, indem Sie die Auswirkungen auf Ihre Core Web Vitals messen.


Übergangstypen in dokumentübergreifenden Übergängen ansehen

Für dokumentübergreifende Ansichtsübergänge werden auch Übergangstypen unterstützt, mit denen sich die Animationen und die erfassten Elemente anpassen lassen.

Wenn Sie beispielsweise in einer Paginierung zur nächsten oder zur vorherigen Seite wechseln, können Sie je nachdem, ob Sie zu einer höheren oder niedrigeren Seite der Sequenz wechseln, unterschiedliche Animationen verwenden.

Wenn Sie diese Typen vorab festlegen möchten, fügen Sie sie in der At-rule @view-transition hinzu:

@view-transition {
  navigation: auto;
  types: slide, forwards;
}

Wenn Sie die Typen „on the fly“ festlegen möchten, verwenden Sie die Ereignisse pageswap und pagereveal, um den Wert von e.viewTransition.types zu ändern.

window.addEventListener("pagereveal", async (e) => {
  if (e.viewTransition) {
    const transitionType = determineTransitionType(navigation.activation.from, navigation.activation.entry);
    e.viewTransition.types.add(transitionType);
  }
});

Die Typen werden nicht automatisch vom ViewTransition-Objekt auf der alten Seite in das ViewTransition-Objekt der neuen Seite übernommen. Sie müssen mindestens für die neue Seite festlegen, welche Typen verwendet werden sollen, damit die Animationen wie erwartet ausgeführt werden.

Verwenden Sie die Pseudoklassenauswahl :active-view-transition-type(), um auf diese Typen zu reagieren, genau wie bei Ansichtsübergängen innerhalb desselben Dokuments.

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

Da Typen nur für einen aktiven Ansichtsübergang gelten, werden sie automatisch entfernt, wenn ein Ansichtsübergang abgeschlossen ist. Daher eignen sich Typen gut für Funktionen wie BFCache.

Demo

In der folgenden Paginierungsdemo wird der Seiteninhalt je nach aufgerufener Seitenzahl vor- oder zurückgeschoben.

Aufzeichnung der Demo zur Paginierung (MPA). Je nachdem, auf welche Seite Sie wechseln, werden unterschiedliche Übergänge verwendet.

Der zu verwendende Übergangstyp wird in den Ereignissen pagereveal und pageswap anhand der „Zu“- und „Von“-URLs bestimmt.

const determineTransitionType = (fromNavigationEntry, toNavigationEntry) => {
  const currentURL = new URL(fromNavigationEntry.url);
  const destinationURL = new URL(toNavigationEntry.url);

  const currentPathname = currentURL.pathname;
  const destinationPathname = destinationURL.pathname;

  if (currentPathname === destinationPathname) {
    return "reload";
  } else {
    const currentPageIndex = extractPageIndexFromPath(currentPathname);
    const destinationPageIndex = extractPageIndexFromPath(destinationPathname);

    if (currentPageIndex > destinationPageIndex) {
      return 'backwards';
    }
    if (currentPageIndex < destinationPageIndex) {
      return 'forwards';
    }

    return 'unknown';
  }
};

Feedback

Wir freuen uns immer über Feedback von Entwicklern. Wenn Sie uns Vorschläge und Fragen senden möchten, erstellen Sie auf GitHub ein Problem für die CSS-Arbeitsgruppe. 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.