Memulai transisi tampilan dengan document.startViewTransition() akan melampirkan overlay ::view-transition ke elemen :root, yang memblokir peristiwa pointer di bagian halaman lainnya, mencegah transisi serentak, dan mengaburkan header position: fixed atau elemen [popover] yang terbuka (bahkan saat grup transisi tampilan bertingkat digunakan untuk mencegah overflow bleeding).
Memanggil element.startViewTransition() pada root komponen mengatasi masalah ini dengan mencakup transisi ke subhierarki DOM tersebut: hierarki elemen semu ::view-transition dimasukkan langsung ke elemen, mempertahankan konteks tumpukan eksternal dan interaktivitas halaman sekaligus memungkinkan beberapa komponen bertransisi secara bersamaan.
Penerapan
1. Memicu Transisi Tercakup
Panggil element.startViewTransition() pada elemen penampung root komponen, bukan document.startViewTransition(). Elemen yang Anda panggil startViewTransition() akan menjadi cakupan transisi (elemen cakupan): hanya elemen dalam subhierarkinya yang diambil, hanya rendering cakupan yang berhenti selama callback update, dan overlay ::view-transition yang dihasilkan cocok dengan ukuran, bentuk, dan konteks tumpukan elemen cakupan.
2. Menamai Elemen yang Berpartisipasi
Tetapkan view-transition-name ke elemen turunan di dalam cakupan yang harus dianimasikan. Anda dapat menggunakan view-transition-name: match-element pada item daftar dinamis untuk otomatis mendapatkan nama transisi setiap elemen dari identitas node DOM-nya, atau menetapkan nama eksplisit (yang hanya perlu unik dalam cakupan tersebut).
Nama diisolasi dalam setiap cakupan, sehingga mencegah konflik di seluruh komponen di berbagai bagian DOM. Namun, view-transition-name dapat bertabrakan dengan nama dalam transisi tampilan tercakup bertingkat. Untuk mencegahnya, tambahkan view-transition-scope: all ke elemen cakupan turunan.
3. Partisipasi Mandiri dan Kliping Overflow
Elemen cakupan otomatis berpartisipasi dalam transisi tampilannya sendiri karena stylesheet agen pengguna menerapkan view-transition-name: root dan view-transition-group: contain ke elemen tersebut. Dengan cara ini, saat geometri cakupan terpengaruh oleh perubahan yang Anda buat di DOM, geometri tersebut akan menganimasikan geometrinya sendiri dengan lancar bersama turunannya, sebagai bagian dari Transisi Tampilan.
Saat cakupan memotong overflow-nya (overflow: hidden, scroll, atau clip), ::view-transition-group-children(root) akan otomatis menerapkan overflow: clip, sehingga mencegah turunan transisi keluar dari batas komponen.
4. Transisi Serentak dan Bertingkat
Sub-pohon komponen yang terpisah dapat menjalankan element.startViewTransition() secara serentak tanpa saling melewati. Cakupan aktif otomatis menerima view-transition-scope: all di stylesheet UA sehingga nilai view-transition-name internal tidak bocor ke transisi luar. Terapkan view-transition-scope: all di CSS pada penampung komponen berulang untuk mencegah transisi luar menangkap nama internal meskipun komponen tidak aktif.
Jika transisi luar perlu memindahkan komponen saat transisi dalamnya aktif, tetapkan view-transition-name ke elemen yang menggabungkan cakupan dalam. Transisi luar kemudian hanya menangkap dan menganimasikan wrapper, bukan elemen cakupan dalam, sehingga transisi dalam dapat berjalan hingga selesai.
Contoh
Tidak diperlukan CSS pada elemen cakupan itu sendiri (.card atau .card ul) untuk menetapkan cakupan—cakupan ditentukan dengan memanggil startViewTransition() pada elemen, bukan document (jika komponen akan disusun bertingkat di dalam transisi tampilan luar, lihat Menjalankan transisi luar untuk 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();
}
});
}
Menjalankan transisi luar
Saat memulai transisi tampilan di luar cakupan yang diinginkan (misalnya, dari komponen induk, atau dari dokumen untuk transisi tingkat halaman), transisi luar akan menangkap dan menganimasikan elemen cakupan dan semua yang ada di dalamnya.
Untuk mencegah hal ini terjadi, gabungkan elemen cakupan dalam elemen wrapper terpisah dengan view-transition-name-nya sendiri, dan terapkan view-transition-scope: all ke elemen cakupan dalam. Hal ini memungkinkan wrapper itu sendiri diambil dan dianimasikan oleh transisi luar sekaligus mencegah konten cakupan dalam diambil.
<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;
}
Atau, Anda dapat mengizinkan transisi luar untuk menangkap turunan. Untuk mempertahankan efek pemangkasan, gunakan Grup Transisi Tampilan Bertingkat untuk memangkas turunan yang diambil di batas wrapper-nya. Namun, dalam hal ini, Anda harus menerapkan clip secara manual ke pseudo ::view-transition-group-children() yang dihasilkan.
.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;
}
Batasan & Aksesibilitas
- WAJIB: Panggil
startViewTransition()pada elemen root komponen, bukandocument, sehingga elemenposition: fixedeksternal dan overlay[popover]tetap berlapis di atas dan bagian halaman lainnya tetap interaktif. - WAJIB: Pertahankan dan pindahkan node DOM yang ada (bukan membuatnya ulang dengan
innerHTML) saat menggunakanview-transition-name: match-element, karenamatch-elementmelacak identitas node DOM. - WAJIB: Hormati preferensi pengguna untuk mengurangi gerakan menggunakan
@media (prefers-reduced-motion: reduce)dengan menyetelanimation: none !importantdi::view-transition-group(*),::view-transition-old(*), dan::view-transition-new(*). - WAJIB: Pertahankan fokus keyboard saat menghapus elemen yang difokuskan selama transisi dengan mengalihkan fokus ke item atau kontrol yang berdekatan di dalam callback update.
- LAKUKAN: Gunakan Grup Transisi Tampilan Bertingkat (
view-transition-group: containdanoverflow: clipdi::view-transition-group-children()) saat elemen yang diambil di dalam cakupan memotong overflow-nya sendiri dan berisi elemen bertingkat yang diambil. - LAKUKAN: Lampirkan
transition.ready.catch(() => {})saat interaksi cepat dapat memicu ulangelement.startViewTransition()pada elemen cakupan yang sama. - LAKUKAN: Gabungkan elemen cakupan dalam dalam elemen wrapper bernama terpisah jika transisi luar perlu menganimasikan posisi komponen saat transisi dalam sedang berjalan.
- JANGAN: Setel
view-transition-name: nonepada elemen cakupan yang memotong overflow-nya (overflow: hidden,scroll, atauclip) atau mengubah geometrinya sendiri, karena memilih tidak ikut partisipasi mandiri akan menonaktifkan pemotongan overflow otomatis.
Strategi penggantian
Jika target Baseline Anda tidak mendukung transisi tampilan cakupan elemen, turunkan performa secara halus dengan segera menjalankan callback update DOM tanpa animasi. Meskipun Anda dapat kembali ke document.startViewTransition(), hal ini tidak direkomendasikan untuk update komponen yang terisolasi, karena transisi cakupan dokumen memblokir transisi komponen serentak dan mengaburkan elemen position: fixed dan [popover] di lapisan atas.
// 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(() => {});
}
Jika Anda benar-benar perlu memiliki penggantian menggunakan document.startViewTransition(), Anda harus memastikan bahwa efek pemangkasan penampung dipertahankan, seperti yang dijelaskan di bagian Menjalankan transisi luar. Perhatikan bahwa pendekatan penggantian ini akan merusak kemampuan untuk menjalankan beberapa transisi tampilan secara serentak dan akan mengaburkan elemen position: fixed dan [popover] di lapisan atas.