Wenn Sie einen Ansichtsübergang mit document.startViewTransition() starten, wird das ::view-transition-Overlay an das :root-Element angehängt. Dadurch werden Zeigerereignisse auf dem Rest der Seite blockiert, gleichzeitige Übergänge verhindert und position: fixed-Header oder offene [popover]-Elemente verdeckt (auch wenn verschachtelte Ansichtsübergangsgruppen verwendet werden, um ein Überlaufen zu verhindern).
Wenn Sie element.startViewTransition() für eine Komponentenwurzel aufrufen, wird der Übergang auf diesen DOM-Unterbaum beschränkt: Der Pseudoelementbaum ::view-transition wird direkt in das Element eingefügt. Dadurch bleiben externe Stapelkontexte und die Interaktivität der Seite erhalten, während mehrere Komponenten gleichzeitig übergehen können.
Implementierung
1. Bereichsbezogenen Übergang auslösen
Rufe element.startViewTransition() für das Stammcontainerelement der Komponente auf, nicht document.startViewTransition(). Das Element, für das Sie startViewTransition() aufrufen, wird zum Bereich des Übergangs (dem Bereichselement): Es werden nur Elemente innerhalb des Unterbaums erfasst, nur das Rendern des Bereichs wird während des Aktualisierungs-Callbacks angehalten und das resultierende ::view-transition-Overlay entspricht der Größe, Form und dem Stapelkontext des Bereichselements.
2. Teilnehmende Elemente benennen
Weisen Sie untergeordneten Elementen innerhalb des Bereichs, die animiert werden sollen, eine view-transition-name zu. Sie können view-transition-name: match-element für dynamische Listenelemente verwenden, um den Übergangsnamen jedes Elements automatisch aus seiner DOM-Knotenidentität abzuleiten, oder explizite Namen zuweisen (die nur innerhalb dieses Bereichs eindeutig sein müssen).
Namen sind innerhalb jedes Bereichs isoliert, um Konflikte zwischen Komponenten in verschiedenen Teilen des DOM zu vermeiden. view-transition-names können jedoch mit Namen in verschachtelten bereichsbezogenen Ansichtsübergängen kollidieren. Um dies zu verhindern, fügen Sie dem untergeordneten Bereichselement view-transition-scope: all hinzu.
3. Selbstbeteiligung und Überlauf-Clipping
Das Bereichselement nimmt automatisch am eigenen Ansichtsübergang teil, da das User-Agent-Stylesheet view-transition-name: root und view-transition-group: contain darauf anwendet. Wenn die Geometrie des Bereichs durch die Änderung im DOM beeinflusst wird, wird die eigene Geometrie zusammen mit den untergeordneten Elementen im Rahmen des Ansichtsübergangs flüssig animiert.
Wenn der Bereich seinen Überlauf abschneidet (overflow: hidden, scroll oder clip), wendet ::view-transition-group-children(root) automatisch overflow: clip an, um zu verhindern, dass untergeordnete Elemente, die sich im Übergang befinden, über die Grenzen der Komponente hinausgehen.
4. Gleichzeitige und verschachtelte Übergänge
Separate Komponenten-Unterbäume können element.startViewTransition() gleichzeitig ausführen, ohne sich gegenseitig zu überspringen. Aktive Bereiche erhalten im UA-Stylesheet automatisch view-transition-scope: all, damit interne view-transition-name-Werte nicht in äußere Übergänge gelangen. Wenden Sie view-transition-scope: all in CSS auf wiederholte Komponentencontainer an, um zu verhindern, dass äußere Übergänge interne Namen erfassen, auch wenn die Komponente inaktiv ist.
Wenn bei einem äußeren Übergang eine Komponente verschoben werden muss, während der innere Übergang aktiv ist, weisen Sie einem Element, das den inneren Bereich umschließt, einen view-transition-name zu. Der äußere Übergang erfasst und animiert dann nur den Wrapper anstelle des inneren Bereichselements, sodass der innere Übergang abgeschlossen werden kann.
Beispiel
Für das Bereichselement selbst (.card oder .card ul) ist kein CSS erforderlich, um den Bereich festzulegen. Der Bereich wird durch Aufrufen von startViewTransition() für das Element anstelle von document bestimmt. Wenn die Komponente in einem äußeren Ansichtsübergang verschachtelt wird, finden Sie weitere Informationen unter Äußeren Übergang ausführen für view-transition-scope: all.
/* 1. Name participating child items */
.card ul li {
view-transition-name: match-element;
}
/* 2. OPTIONAL: Customize transition timing (300ms is an example duration) */
::view-transition-group(*) {
animation-duration: 300ms;
animation-timing-function: ease-in-out;
}
/* 3. MANDATORY: Respect user preference for reduced motion */
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}
// 1. Trigger a transition scoped to a single component subtree
function runScopedTransition(scopeEl, updateDOM) {
// MANDATORY: Call startViewTransition on the component element, not document
const transition = scopeEl.startViewTransition(() => {
updateDOM();
});
// Re-triggering startViewTransition() on the same element skips the active transition
// and rejects `ready`. Catch the rejection to avoid unhandled promise rejection errors.
transition.ready.catch(() => {});
}
// 2. Reorder items by moving existing DOM nodes (required for `match-element`)
// Scope to `listEl` since only the list order changes, keeping controls outside `listEl` interactive
function reorderItems(listEl, orderedIds) {
const existing = new Map(
Array.from(listEl.children, (li) => [li.dataset.id, li]),
);
runScopedTransition(listEl, () => {
for (const id of orderedIds) {
const li = existing.get(id);
if (li) listEl.appendChild(li);
}
});
}
// 3. Remove an item while preserving exit animation and keyboard focus
// Scope to the outer `cardEl` so the card itself also transitions its height change
function removeItem(cardEl, itemEl) {
const hadFocus = itemEl.contains(document.activeElement);
const nextFocusTarget =
itemEl.nextElementSibling?.querySelector('button') ??
itemEl.previousElementSibling?.querySelector('button') ??
cardEl.querySelector('button');
runScopedTransition(cardEl, () => {
itemEl.remove();
if (hadFocus) {
nextFocusTarget?.focus();
}
});
}
Äußeren Übergang ausführen
Wenn Sie einen Ansichtsübergang außerhalb des vorgesehenen Bereichs starten (z.B. von einer übergeordneten Komponente oder vom Dokument für einen Übergang auf Seitenebene), werden das Bereichselement und alles darin vom äußeren Übergang erfasst und animiert.
Um dies zu verhindern, umschließen Sie das Bereichselement mit einem separaten Wrapper-Element mit einem eigenen view-transition-name und wenden Sie view-transition-scope: all auf das innere Bereichselement an. Dadurch kann der Wrapper selbst vom äußeren Übergang erfasst und animiert werden, während die Inhalte des inneren Bereichs nicht erfasst werden.
<div class="page" id="page">
<div class="card-wrapper">
<section class="card" id="left">
<h2>Card A</h2>
<ul id="left-list">
<li>…</li>
</ul>
</section>
</div>
<div class="card-wrapper">
<section class="card" id="right">
<h2>Card B</h2>
<ul id="right-list">
<li>…</li>
</ul>
</section>
</div>
</div>
document.querySelector('#page').startViewTransition(() => {
// do your DOM updates here...
});
/* Apply a view-transition-name on the wrapper */
.card-wrapper {
view-transition-name: match-element;
}
/* Prevent the contents of the card being captured by an outer transition */
.card {
view-transition-scope: all;
}
.card ul li {
view-transition-name: match-element;
}
Alternativ können Sie zulassen, dass der äußere Übergang die untergeordneten Elemente erfasst. Wenn Sie Clipping-Effekte beibehalten möchten, verwenden Sie verschachtelte Ansichtsübergangsgruppen, um die erfassten untergeordneten Elemente an den Grenzen ihres Wrappers zu beschneiden. In diesem Fall müssen Sie die clip jedoch manuell auf das resultierende ::view-transition-group-children()-Pseudoelement anwenden.
.card-wrapper {
overflow: clip;
view-transition-name: match-element; /* Capture me as part of the outer VT */
view-transition-group: contain; /* Nest children that also have a view-transition-name */
view-transition-class: card; /* For targeting purposes */
}
/* Manually copy back the clip onto the ::view-transition-group-children pseudo */
::view-transition-group-children(.card) {
overflow: clip;
}
.card ul li {
view-transition-name: match-element;
}
Einschränkungen und Barrierefreiheit
- OBLIGATORISCH: Rufen Sie
startViewTransition()für das Stammelement der Komponente auf, nicht fürdocument. So bleiben externeposition: fixed-Elemente und[popover]-Overlays darüber und der Rest der Seite bleibt interaktiv. - OBLIGATORISCH: Vorhandene DOM-Knoten beibehalten und verschieben (anstatt sie mit
innerHTMLneu zu erstellen), wennview-transition-name: match-elementverwendet wird, damatch-elementdie DOM-Knotenidentität verfolgt. - OBLIGATORISCH: Berücksichtigen Sie die Nutzereinstellungen für reduzierte Bewegung mit
@media (prefers-reduced-motion: reduce), indem Sieanimation: none !importantfür::view-transition-group(*),::view-transition-old(*)und::view-transition-new(*)festlegen. - OBLIGATORISCH: Der Tastaturfokus muss beibehalten werden, wenn ein fokussiertes Element während eines Übergangs entfernt wird. Verschieben Sie dazu den Fokus im Update-Callback auf ein benachbartes Element oder Steuerelement.
- EMPFOHLEN: Verwenden Sie verschachtelte Ansichtsübergangsgruppen (
view-transition-group: containundoverflow: clipfür::view-transition-group-children()), wenn ein erfasstes Element innerhalb des Bereichs seinen eigenen Überlauf abschneidet und verschachtelte erfasste Elemente enthält. - EMPFOHLEN: Hängen Sie
transition.ready.catch(() => {})an, wenn schnelle Interaktionenelement.startViewTransition()für dasselbe Bereichselement noch einmal auslösen können. - EMPFOHLEN: Schließen Sie ein inneres Bereichselement in ein separates benanntes Wrapper-Element ein, wenn ein äußerer Übergang die Position der Komponente animieren muss, während ein innerer Übergang ausgeführt wird.
- NICHT:
view-transition-name: nonefür ein Bereichselement festlegen, das seinen Überlauf abschneidet (overflow: hidden,scrolloderclip) oder seine eigene Geometrie ändert, da durch die Deaktivierung der Selbstbeteiligung das automatische Abschneiden des Überlaufs deaktiviert wird.
Fallback-Strategien
Wenn Ihr Baseline-Ziel keine elementbezogenen Ansichtsübergänge unterstützt, sollten Sie die DOM-Aktualisierung sofort ohne Animation ausführen. Sie könnten zwar auf document.startViewTransition() zurückgreifen, dies wird jedoch für isolierte Komponentenaktualisierungen nicht empfohlen, da ein Übergang auf Dokumentebene gleichzeitige Komponentenübergänge blockiert und position: fixed- und [popover]-Elemente in der obersten Ebene verdeckt.
// Feature detect specifically on Element.prototype (not document.startViewTransition)
const supportsScopedTransition = 'startViewTransition' in Element.prototype;
function runWithTransition(scopeEl, updateDOM) {
if (!supportsScopedTransition) {
// Fallback: apply the DOM update immediately without animation
updateDOM();
return;
}
const transition = scopeEl.startViewTransition(() => {
updateDOM();
});
transition.ready.catch(() => {});
}
Wenn Sie unbedingt einen Fallback mit document.startViewTransition() benötigen, müssen Sie dafür sorgen, dass die Clipping-Effekte von Containern erhalten bleiben, wie im Abschnitt Äußeren Übergang ausführen beschrieben. Beachten Sie, dass dieser Fallback-Ansatz die Möglichkeit, mehrere Ansichtsübergänge gleichzeitig auszuführen, beeinträchtigt und position: fixed- und [popover]-Elemente in der obersten Ebene verdeckt.