Mencegah masalah pemangkasan (dan lainnya) dalam transisi tampilan dengan menggunakan grup transisi tampilan bertingkat

Dipublikasikan: 22 Sep 2025

Saat Anda memulai transisi tampilan, browser akan otomatis mengambil snapshot sebelum dan sesudah elemen yang diberi tag view-transition-name. Snapshot ini dirender dalam struktur elemen semu. Secara default, hierarki yang dihasilkan adalah "datar". Artinya, hierarki asli di DOM akan hilang, dan semua grup transisi tampilan yang diambil menjadi elemen saudara di bawah satu elemen semu ::view-transition.

Pendekatan hierarki datar ini sudah cukup untuk banyak kasus penggunaan, tetapi ada beberapa kasus penggunaan gaya yang tidak dapat dicapai dengannya. Berikut adalah contoh efek yang dapat memiliki efek visual yang tidak terduga dalam hierarki datar:

  • Pemangkasan (overflow, clip-path, border-radius): pemangkasan memengaruhi elemen turunan, yang berarti bahwa elemen sekelompok transisi tampilan tidak dapat saling memangkas.
  • opacity, mask-image, dan filter: demikian pula, efek ini dirancang untuk berfungsi pada gambar pohon yang sepenuhnya di-raster, memengaruhi turunan, bukan memengaruhi setiap item satu per satu.
  • Transformasi 3D (transform-style, transform, perspective): untuk menampilkan rentang penuh animasi transformasi 3D, beberapa hierarki harus dipertahankan.

Contoh berikut menunjukkan pseudo-tree datar, dengan elemen yang dipangkas oleh ancestor di pohon DOM. Elemen ini kehilangan klipingnya selama transisi tampilan, sehingga menghasilkan efek visual yang rusak.

Perekaman efek kliping yang rusak saat transisi tampilan berjalan. Teks seharusnya dipangkas oleh dialog, tetapi tidak. Waktu animasi diperlambat untuk melebih-lebihkan efeknya.

Grup transisi tampilan bertingkat adalah ekstensi untuk transisi tampilan yang memungkinkan Anda menyusun elemen semu ::view-transition-group satu sama lain. Saat grup transisi tampilan disarangkan, efek seperti pemangkasan dapat dipulihkan selama transisi.

Browser Support

  • Chrome: 140.
  • Edge: not supported.
  • Firefox: not supported.
  • Safari: not supported.

Dari pseudo-tree datar ke pseudo-tree bertingkat

Dalam demo berikut, Anda dapat mengklik avatar seseorang untuk melihat info selengkapnya tentang orang tersebut. Animasi ditangani oleh transisi tampilan dokumen yang sama yang mengubah tombol yang diklik menjadi dialog, memindahkan avatar dan nama di seluruh layar, serta menggeser paragraf dari dialog ke atas atau ke bawah.

Demo Langsung

Rekaman Demo

Rekaman Demo (Diperlambat)

Jika Anda melihat demo dengan cermat, Anda akan melihat bahwa ada masalah dengan transisi: meskipun paragraf dengan deskripsi adalah turunan dari elemen <dialog> dalam DOM, teks tidak dipangkas oleh kotak <dialog> selama transisi:

<dialog id="info_bramus" closedby="any">
  <h2><img alt="…" class="avatar" height="96" width="96" src="avatar_bramus.jpg"> <span>Bramus</span></h2>
  <p>Bramus is …</p>
  <p>…</p>
</dialog>

Menerapkan overflow: clip pada <dialog> juga tidak akan melakukan apa pun.

Masalahnya adalah cara transisi tampilan membangun dan merender pseudo tree-nya:

  • Dalam pseudo-tree, secara default, semua snapshot adalah saudara kandung satu sama lain.
  • Pseudo-tree dirender dalam elemen semu ::view-transition yang dirender di atas seluruh dokumen.

Khusus untuk demo ini, hierarki DOM terlihat seperti berikut:

html
  ├─ ::view-transition
  │  ├─ ::view-transition-group(card)
  │  │  └─ ::view-transition-image-pair(card)
  │  │     ├─ ::view-transition-old(card)
  │  │     └─ ::view-transition-new(card)
  │  ├─ ::view-transition-group(name)
  │  │  └─ ::view-transition-image-pair(name)
  │  │     ├─ ::view-transition-old(name)
  │  │     └─ ::view-transition-new(name)
  │  ├─ ::view-transition-group(avatar)
  │  │  └─ ::view-transition-image-pair(avatar)
  │  │     ├─ ::view-transition-old(avatar)
  │  │     └─ ::view-transition-new(avatar)
  │  ├─ ::view-transition-group(paragraph1.text)
  │  │  └─ ::view-transition-image-pair(paragraph1.text)
  │  │     └─ ::view-transition-new(paragraph1.text)
  │  └─ ::view-transition-group(paragraph2.text)
  │     └─ ::view-transition-image-pair(paragraph2.text)
  │        └─ ::view-transition-new(paragraph2.text)
  ├─ head
  └─ body
        └─ …

Karena pseudo ::view-transition-group(.text) adalah saudara kandung yang berhasil dari pseudo ::view-transition-group(card), pseudo ::view-transition-group(.text) akan digambar di atas kartu.

Agar memiliki klip ::view-transition-group(card) ::view-transition-group(.text), pseudo ::view-transition-group(.text) harus merupakan turunan dari ::view-transition-group(card). Untuk melakukannya, gunakan view-transition-group yang memungkinkan Anda menetapkan "grup induk" untuk elemen semu ::view-transition-group() yang dihasilkan.

Untuk mengubah grup induk, Anda memiliki dua opsi:

  • Pada induk, tetapkan view-transition-group ke contain, agar induk berisi semua turunan dengan view-transition-name.
  • Pada semua turunan, tetapkan view-transition-group ke view-transition-name induk. Anda juga dapat menggunakan nearest untuk menargetkan grup induk terdekat.

Jadi, untuk demo ini, guna menggunakan grup transisi tampilan bertingkat, kodenya menjadi:

button.clicked,
dialog {
  view-transition-group: contain;
}

Atau

button.clicked,
dialog *,
  view-transition-group: nearest;
}

Dengan adanya kode ini, pseudo ::view-transition-group(.text) kini disarangkan di dalam pseudo ::view-transition-group(card). Hal ini dilakukan dalam pseudo ::view-transition-group-children(…) tambahan, yang menjaga semua pseudo bertingkat tetap bersama:

html
  ├─ ::view-transition
  │  ├─ ::view-transition-group(card)
  │  │  ├─ ::view-transition-image-pair(card)
  │  │  │  ├─ ::view-transition-old(card)
  │  │  │  └─ ::view-transition-new(card)
  │  │  └─::view-transition-group-children(card)
  │  │    ├─ ::view-transition-group(paragraph1.text)
  │  │    │  └─ ::view-transition-image-pair(paragraph1.text)
  │  │    │     └─ ::view-transition-new(paragraph1.text)
  │  │    └─ ::view-transition-group(paragraph2.text)
  │  │       └─ ::view-transition-image-pair(paragraph2.text)
  │  │          └─ ::view-transition-new(paragraph2.text)
  │  ├─ ::view-transition-group(name)
  │  │  └─ ::view-transition-image-pair(name)
  │  │     ├─ ::view-transition-old(name)
  │  │     └─ ::view-transition-new(name)
  │  └─ ::view-transition-group(avatar)
  │     └─ ::view-transition-image-pair(avatar)
  │        ├─ ::view-transition-old(avatar)
  │        └─ ::view-transition-new(avatar)
  ├─ head
  └─ body
        └─ …

Terakhir, agar ::view-transition-group(card) memangkas paragraf secara semu, terapkan overflow: clip ke pseudo ::view-transition-group-children(card):

::view-transition-group-children(card) {
  overflow: clip;
}

Hasilnya adalah sebagai berikut:

Demo Langsung

Rekaman Demo

Rekaman Demo (Diperlambat)

Pseudo ::view-transition-group-children hanya ada saat grup bertingkat digunakan. Ukuran elemen ini disesuaikan dengan border-box elemen asli dan diberi batas transparan dengan bentuk dan ketebalan batas yang sama dengan elemen yang menghasilkan elemen semu—card dalam contoh sebelumnya.

Kliping dan lainnya

Grup transisi tampilan bertingkat digunakan di tempat selain efek pemangkasan. Contoh lainnya adalah efek 3D. Dalam demo berikut, ada opsi untuk memutar kartu dalam 3D selama transisi.

html:active-view-transition-type(open) {
    &::view-transition-old(card) {
        animation-name: rotate-out;
    }
    &::view-transition-new(card) {
        animation-name: rotate-in;
    }
}
html:active-view-transition-type(close) {
    &::view-transition-old(card) {
        animation-name: rotate-in;
    }
    &::view-transition-new(card) {
        animation-name: rotate-out;
    }
}

Tanpa grup transisi tampilan bertingkat, avatar dan nama tidak berputar bersama kartu.

Demo Langsung

Rekaman Demo

Rekaman Demo (Diperlambat)

Dengan menyusun avatar dan nama semu di dalam kartu, efek 3D dapat dipulihkan. Namun, bukan hanya itu yang perlu Anda lakukan. Selain memutar pseudo ::view-transition-old(card) dan ::view-transition-new(card), Anda juga perlu memutar pseudo ::view-transition-group-children(card).

html:active-view-transition-type(open) {
    &::view-transition-group-children(card) {
        animation: rotate-in var(--duration) ease;
        backface-visibility: hidden;
    }
}
html:active-view-transition-type(close) {
    &::view-transition-group-children(card) {
        animation: rotate-out var(--duration) ease;
        backface-visibility: hidden;
    }
}

Demo Langsung

Rekaman Demo

Rekaman Demo (Diperlambat)

Demo lainnya

Dalam contoh berikut, grup transisi tampilan bertingkat digunakan untuk memastikan kartu dipangkas oleh scroller induknya. Anda dapat mengaktifkan atau menonaktifkan penggunaan grup transisi tampilan bertingkat menggunakan kontrol yang disertakan.

Demo Langsung

Rekaman Demo

Hal menarik tentang demo ini adalah semua pseudo ::view-transition-group(.card) disarangkan di dalam—dan dipangkas oleh—pseudo ::view-transition-group(cards) induk. #targeted-card dikecualikan karena animasi masuk/keluarnya tidak boleh terpotong oleh ::view-transition-group(cards).

/* The .cards wrapper contains all children */
.cards {
  view-transition-name: cards;
  view-transition-group: contain;
}

/* Contents that bleed out get clipped */
&::view-transition-group-children(cards) {
  overflow: clip;
}

/* Each card is given a v-t-name and v-t-class */
.card {
  view-transition-name: match-element;
  view-transition-class: card;
}

/* The targeted card is given a unique name (to style the pseudo differently)
   and shouldn't be contained by the ::view-transition-group-children(cards) pseudo */
#targeted-card {
  view-transition-name: targeted-card;
  view-transition-group: none;
}

Rekap

Transisi tampilan bertingkat memungkinkan Anda mempertahankan beberapa topologi hierarki DOM saat membuat elemen semu. Hal ini memungkinkan berbagai efek yang sebelumnya tidak mungkin dilakukan dengan transisi tampilan, beberapa di antaranya kami jelaskan di sini.

Penyusunan bertingkat mengubah model cara transisi tampilan dibuat, dan dimaksudkan untuk digunakan dalam membuat efek lanjutan. Seperti yang disebutkan, transisi tampilan cakupan elemen juga dapat menghasilkan sebagian efek dengan model yang lebih sederhana. Sebaiknya coba kedua fitur ini untuk memutuskan mana yang paling sesuai dengan kebutuhan Anda.