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

Dipublikasikan: 22 September 2025

Saat Anda memulai transisi tampilan, browser akan otomatis mengambil snapshot sebelum dan sesudah elemen yang diberi tag dengan view-transition-name. Snapshot ini dirender dalam hierarki elemen semu. Secara default, hierarki yang dihasilkan adalah "flat". Artinya, hierarki asli dalam DOM akan hilang, dan semua grup transisi tampilan yang diambil adalah turunan 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 dengan pendekatan ini. Berikut adalah contoh efek yang dapat memiliki efek visual yang tidak diharapkan dalam hierarki datar:

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

Contoh berikut menunjukkan hierarki semu datar, dengan elemen yang diklip oleh ancestor dalam hierarki DOM. Elemen ini kehilangan klipingnya selama transisi tampilan, sehingga menghasilkan efek visual yang rusak.

Rekaman efek pemangkasan yang rusak saat transisi tampilan berjalan. Teks seharusnya terpotong oleh dialog, tetapi tidak. Waktu animasi diperlambat untuk memperjelas efeknya.

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

Browser Support

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

Dari pohon semu datar ke pohon semu 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, dan menggeser paragraf dari dialog ke atas atau ke bawah.

Demo Langsung

Rekaman Demo

Rekaman Demo (Versi lambat)

Jika Anda melihat demo dengan cermat, Anda akan melihat bahwa ada masalah dengan transisi: meskipun paragraf dengan deskripsi adalah turunan dari elemen <dialog> di DOM, teks tidak dipotong 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 berpengaruh apa pun.

Masalahnya adalah cara transisi tampilan membangun dan merender hierarki semu:

  • Dalam pseudo-tree, secara default, semua snapshot adalah setara satu sama lain.
  • Pseudo-tree dirender dalam elemen pseudo ::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 elemen seinduk yang berhasil dari pseudo ::view-transition-group(card), pseudo tersebut digambar di atas kartu.

Agar ::view-transition-group(card) memotong ::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 berisi semua turunan dengan view-transition-name.
  • Di semua turunan, tetapkan view-transition-group ke view-transition-name induk. Anda juga dapat menggunakan nearest untuk menargetkan grup ancestor terdekat.

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

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

Atau

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

Dengan kode ini, pseudo ::view-transition-group(.text) kini disusun bertingkat di dalam pseudo ::view-transition-group(card). Hal ini dilakukan dalam pseudo ::view-transition-group-children(…) tambahan, yang menyatukan semua pseudo bertingkat:

html
  ├─ ::view-transition
  │  ├─ ::view-transition-group(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-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)
  ├─ head
  └─ body
        └─ …

Terakhir, agar pseudo ::view-transition-group(card) memotong paragraf, 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 (Versi lambat)

Pseudo ::view-transition-group-children hanya ada jika grup bertingkat digunakan. Ukurannya disesuaikan dengan kotak batas elemen asli dan diberi batas transparan dengan bentuk dan ketebalan batas yang sama dengan elemen yang menghasilkan elemen pseudo—card dalam contoh sebelumnya.

Pemotongan 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 akan berputar bersama kartu.

Demo Langsung

Rekaman Demo

Rekaman Demo (Versi lambat)

Dengan menyusun bertingkat pseudo avatar dan nama di dalam kartu, efek 3D dapat dipulihkan. Namun, itu bukan satu-satunya hal yang perlu Anda lakukan. Selain merotasi pseudo ::view-transition-old(card) dan ::view-transition-new(card), Anda juga perlu merotasi 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 (Versi lambat)

Demo lainnya

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

Demo Langsung

Rekaman Demo

Hal yang menarik dari demo ini adalah semua pseudo ::view-transition-group(.card) disusun bertingkat di dalam—dan diklip oleh—pseudo ::view-transition-group(cards) ancestor. #targeted-card dikecualikan karena animasi masuk/keluarnya tidak boleh dipotong 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;
}

Rangkuman

Transisi tampilan bertingkat memungkinkan Anda mempertahankan beberapa topologi hierarki DOM saat membuat elemen pseudo. 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 guna membuat efek lanjutan. Seperti yang telah disebutkan, transisi tampilan cakupan elemen juga dapat mencapai subset efek dengan model yang lebih sederhana. Sebaiknya coba kedua fitur tersebut untuk menentukan mana yang paling sesuai dengan kebutuhan Anda.