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

Ein Aufrufübergang zwischen zwei verschiedenen Dokumenten wird als dokumentübergreifender Übergang bezeichnet. Dies ist normalerweise bei mehrseitigen Anwendungen (MPA) der Fall. Ab Chrome 126 werden in Chrome dokumentübergreifende Übergänge für die Ansicht unterstützt.

Unterstützte Browser

  • Chrome: 126. <ph type="x-smartling-placeholder">
  • Edge: 126. <ph type="x-smartling-placeholder">
  • Firefox: nicht unterstützt <ph type="x-smartling-placeholder">
  • Safari: nicht unterstützt. <ph type="x-smartling-placeholder">

Dokumentübergreifende Übergänge basieren auf denselben Bausteinen und Prinzipien wie Übergänge bei Ansichten in demselben Dokument. Dies ist sehr bewusst:

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

Der Unterschied zu Ansichtsübergängen für dasselbe Dokument besteht darin, dass Sie bei dokumentübergreifenden Änderungen der Ansicht nicht document.startViewTransition aufrufen müssen, um einen Ansichtsübergang zu starten. Der Trigger für eine dokumentübergreifende Ansichtsumstellung ist stattdessen eine Navigation am selben Ursprung von einer Seite zur anderen. Diese Aktion wird normalerweise durch den Nutzer Ihrer Website ausgeführt, der auf einen Link klickt.

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 denselben Ursprung haben.
  • Beide Seiten müssen aktiviert werden, damit ein Wechsel der Ansicht möglich ist.

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


Dokumentübergreifende Übergänge in der Ansicht sind auf Navigationen am selben Ursprung beschränkt

Dokumentübergreifende Übergänge in der Ansicht sind nur auf Navigationen mit demselben Ursprung beschränkt. Eine Navigation gilt als derselbe Ursprung, wenn der Ursprung beider teilnehmenden Seiten gleich ist.

Der Ursprung einer Seite ist eine Kombination aus verwendetem Schema, Hostname und Port, wie auf web.dev beschrieben.

<ph type="x-smartling-placeholder">
</ph> Eine Beispiel-URL, in der Schema, Hostname und Port hervorgehoben sind. In Kombination bilden sie den Ursprung.
Eine Beispiel-URL, in der Schema, Hostname und Port hervorgehoben sind. In Kombination bilden sie den Ursprung.

So kann es beispielsweise beim Wechsel von developer.chrome.com nach developer.chrome.com/blog zu einem dokumentübergreifenden Ansichtswechsel kommen, da diese beiden Quellen denselben Ursprung haben. Bei der Navigation von developer.chrome.com nach www.chrome.com ist dieser Übergang nicht möglich, da diese ursprungsübergreifend und zur selben Website gehören.


Der Wechsel der dokumentübergreifenden Ansicht ist optional.

Damit ein dokumentübergreifender Wechsel zwischen zwei Dokumenten möglich ist, müssen beide teilnehmenden Seiten dies zulassen. Dies erfolgt mit der @view-transition-at-Regel in CSS.

Legen Sie in der At-Regel @view-transition den Deskriptor navigation auf auto fest, um Ansichtsübergänge für dokumentübergreifende Navigation am selben Ursprung zu ermöglichen.

@view-transition {
  navigation: auto;
}

Wenn Sie den navigation-Deskriptor auf auto festlegen, werden für die folgenden NavigationType Ansichtenübergänge zugelassen:

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

Von auto ausgeschlossene Navigationen sind z. B. das Navigieren über die URL-Adressleiste oder das Klicken auf ein Lesezeichen sowie jede Art von Aktualisierung durch Nutzer oder Skripts.

Wenn eine Navigation zu lange dauert – im Fall von Chrome mehr als vier Sekunden – wird der Übergang mit TimeoutError DOMException übersprungen.

Demo zum Wechsel der dokumentübergreifenden Ansicht

In der folgenden Demo werden Ansichtsübergänge verwendet, um eine Stack Navigator-Demo zu erstellen. Hier gibt es keine Aufrufe für document.startViewTransition(). Die Übergänge bei der Ansicht werden durch das Wechseln von einer Seite zu einer anderen ausgelöst.

<ph type="x-smartling-placeholder">
</ph>
Aufnahme der Stack Navigator-Demo. Erfordert Chrome 126 oder höher.

Übergänge dokumentübergreifender Ansichten anpassen

Zum Anpassen von dokumentübergreifenden Übergängen der Ansicht können Sie einige Webplattformfunktionen verwenden.

Diese Funktionen sind nicht Teil der View Transition API-Spezifikation selbst, sondern für die gemeinsame Verwendung mit ihr vorgesehen.

Die Ereignisse pageswap und pagereveal

Unterstützte Browser

  • Chrome: 124. <ph type="x-smartling-placeholder">
  • Edge: 124. <ph type="x-smartling-placeholder">
  • Firefox: nicht unterstützt <ph type="x-smartling-placeholder">
  • Safari: wird nicht unterstützt. <ph type="x-smartling-placeholder">

Quelle

Die HTML-Spezifikation enthält zwei neue Ereignisse, die Sie verwenden können, um dokumentübergreifende Übergänge bei Ansichten anzupassen: pageswap und pagereveal.

Diese beiden Ereignisse werden für jede dokumentübergreifende Navigation am selben Ursprung ausgelöst, unabhängig davon, ob ein Aufrufübergang stattfindet oder nicht. Wenn ein Ansichtsübergang zwischen den beiden Seiten bevorsteht, können Sie bei diesen Ereignissen über die Eigenschaft viewTransition auf das ViewTransition-Objekt zugreifen.

  • Das pageswap-Ereignis wird ausgelöst, bevor der letzte Frame einer Seite gerendert wird. Damit können Sie in letzter Minute Änderungen auf der ausgehenden Seite vornehmen, bevor die alten Snapshots erstellt werden.
  • Das pagereveal-Ereignis wird auf einer Seite ausgelöst, nachdem sie initialisiert oder reaktiviert wurde, jedoch vor der ersten Rendering-Empfehlung. Damit können Sie die neue Seite anpassen, bevor die neuen Snapshots erstellt werden.

Sie können diese Ereignisse beispielsweise verwenden, um schnell einige view-transition-name-Werte festzulegen oder zu ändern oder Daten von einem Dokument in ein anderes zu übertragen. Dazu werden Daten aus sessionStorage geschrieben und gelesen, um den Ansichtsübergang anzupassen, bevor der Übergang tatsächlich ausgeführt wird.

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

Wenn Sie möchten, können Sie 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. Sie gehen auch unterschiedlich mit den verschiedenen Versprechen um:

  • pageswap: Sobald das Dokument ausgeblendet ist, wird das alte ViewTransition-Objekt übersprungen. In diesem Fall lehnt viewTransition.ready den Vorgang ab und viewTransition.finished wird aufgelöst.
  • pagereveal: Das Versprechen von updateCallBack ist an diesem Punkt bereits erledigt. Du kannst die Promise viewTransition.ready und viewTransition.finished verwenden.

Unterstützte Browser

  • Chrome: 123. <ph type="x-smartling-placeholder">
  • Edge: 123. <ph type="x-smartling-placeholder">
  • Firefox: nicht unterstützt <ph type="x-smartling-placeholder">
  • Safari: nicht unterstützt. <ph type="x-smartling-placeholder">

Quelle

Sowohl beim Ereignis pageswap als auch beim Ereignis pagereveal können Sie Aktionen basierend auf den URLs der alten und der neuen Seite ausführen.

Beispielsweise hängt die Art der zu verwendenden Animation im MPA Stack Navigator vom Navigationspfad ab:

  • Wenn Sie von der Übersichtsseite zur Detailseite wechseln, muss der neue Inhalt von rechts nach links eingeschoben werden.
  • Wenn Sie von der Detailseite zur Übersichtsseite wechseln, muss der alte Inhalt von links nach rechts herausfallen.

Dazu benötigst du Informationen über die Navigation, die im Fall von pageswap bevorsteht bzw. im Fall von pagereveal gerade erfolgt ist.

Dazu können Browser jetzt NavigationActivation-Objekte verfügbar machen, die Informationen zur Navigation am selben Ursprung enthalten. Dieses Objekt gibt den verwendeten Navigationstyp, die aktuellen Einträge und die endgültigen Zielverlaufseinträge aus navigation.entries() der Navigation API an.

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

Sehen Sie sich diese Profile-Demo an, in der NavigationActivation-Informationen in den Ereignissen pageswap und pagereveal verwendet werden, um view-transition-name-Werte für die Elemente festzulegen, die am Aufrufübergang beteiligt sein müssen.

So musst du nicht jeden einzelnen Artikel in der Liste im Voraus mit einem view-transition-name dekorieren. Stattdessen geschieht dies Just-in-Time mit JavaScript, nur bei Elementen, die es benötigen.

<ph type="x-smartling-placeholder">
</ph>
Aufzeichnung der Profile-Demo. Erfordert Chrome 126 oder höher.

Der Code lautet wie folgt:

// 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 wird auch automatisch bereinigt, indem die view-transition-name-Werte nach Ausführung des Ansichtsübergangs entfernt werden. Auf diese Weise ist die Seite für aufeinanderfolgende Navigationen bereit und kann auch den Verlauf des Verlaufs durchlaufen.

Verwenden Sie dazu diese Dienstfunktion, die view-transition-names vorübergehend festlegt.

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

Mit Blockierung des Renderings warten, bis Inhalte geladen werden

Unterstützte Browser

  • Chrome: 124. <ph type="x-smartling-placeholder">
  • Edge: 124. <ph type="x-smartling-placeholder">
  • Firefox: nicht unterstützt <ph type="x-smartling-placeholder">
  • Safari: nicht unterstützt. <ph type="x-smartling-placeholder">

In einigen Fällen kann es sinnvoll sein, das erste Rendern einer Seite zu warten, bis ein bestimmtes Element im neuen DOM vorhanden ist. So wird Blinken vermieden und der Status der Animation muss stabil sein.

Definiere im <head> eine oder mehrere Element-IDs, die vorhanden sein müssen, bevor die Seite zum ersten Mal gerendert wird. Verwende dazu das folgende Meta-Tag.

<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 soll. Bei Bildern reicht beispielsweise das bloße Vorhandensein des <img>-Tags mit dem angegebenen id im DOM-Baum aus, damit die Bedingung als „true“ ausgewertet wird. Möglicherweise wird das Bild noch geladen.

Bevor Sie sich für eine Komplettblockierung entscheiden, sollten Sie sich bewusst sein, dass das inkrementelle Rendering ein grundlegender Aspekt des Webs ist. Seien Sie also vorsichtig, wenn Sie das Rendering blockieren. Welche Auswirkungen das Blockieren des Renderings hat, muss von Fall zu Fall bewertet werden. Standardmäßig sollten Sie blocking=render nur dann verwenden, wenn Sie die Auswirkungen auf Ihre Nutzer aktiv messen und messen können, indem Sie die Auswirkungen auf Ihre Core Web Vitals messen.


Übergangstypen bei dokumentübergreifenden Übergängen der Ansicht ansehen

Bei dokumentübergreifenden Ansichtsübergängen werden auch Arten von Ansichten unterstützt, mit denen die Animationen und die erfassten Elemente angepasst werden können.

Wenn Sie beispielsweise bei einer Paginierung zur nächsten oder vorherigen Seite wechseln, können Sie verschiedene Animationen verwenden, je nachdem, ob Sie zu einer höheren oder einer niedrigeren Seite aus der Abfolge springen.

Wenn Sie diese Typen im Voraus festlegen möchten, fügen Sie die Typen in der At-Regel @view-transition hinzu:

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

Wenn Sie die Typen spontan 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 der alten Seite in das ViewTransition-Objekt der neuen Seite übernommen. Sie müssen mindestens auf der neuen Seite den Typ bzw. die Typen bestimmen, die verwendet werden sollen, damit die Animationen erwartungsgemäß ausgeführt werden.

Um auf diese Typen zu reagieren, verwenden Sie den Pseudoklassenselektor :active-view-transition-type() auf dieselbe Weise wie bei Übergängen bei der Ansicht für das gleiche Dokument.

/* 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 Active View-Übergang gelten, werden Typen automatisch bereinigt, wenn ein Ansichtsübergang abgeschlossen ist. Daher funktionieren Typen gut mit Funktionen wie BFCache.

Demo

In der folgenden Demo zur Paginierung wird der Seiteninhalt je nach Seitennummer, die Sie aufrufen, vor- oder zurückgeschoben.

<ph type="x-smartling-placeholder">
</ph>
Aufzeichnung der Paginations-Demo (MPA). Je nachdem, zu welcher Seite Sie wechseln, werden unterschiedliche Übergänge verwendet.

Der zu verwendende Übergangstyp wird in den Ereignissen pagereveal und pageswap anhand der URLs zu und von dieser festgelegt.

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 etwas teilen möchten, melden Sie ein Problem bei der CSS Working Group auf GitHub und geben Sie dabei Vorschläge und Fragen. Stellen Sie dem Problem [css-view-transitions] voran. Sollten Sie auf einen Fehler stoßen, melden Sie den Fehler in Chromium.