View Transitions API — Web Animation

View Transitions API View Transitions API adalah cara baru (Chrome 111+, Maret 2023) untuk membuat transisi halus antara dua state UI — tanpa harus repot…

View Transitions API membuat perpindahan antara dua keadaan halaman beranimasi tanpa kamu menulis animasi keluar dan masuk satu per satu. Bayangin foto sebelum-sesudah renovasi kamar: fotografer memotret kamar lama, tukang mengganti isinya, fotografer memotret lagi, lalu dua foto itu ditumpuk dan dipudarkan dari yang lama ke yang baru. Browser melakukan persis itu untuk DOM-mu.

🧪 Tebakan cepat: Di galeri Kamar Teras, foto besar diberi view-transition-name: foto. Supaya thumbnail yang terpilih "terbang" ke foto besar, kamu juga memberi thumbnail itu view-transition-name: foto. Lalu kamu memanggil startViewTransition untuk ganti foto. Apa yang terjadi? Jawabannya ada di bagian "Satu nama, satu elemen".

document.startViewTransition(update)

function gantiFoto(i) {
  const update = () => renderFoto(i);     // ubah DOM di sini
  if (!document.startViewTransition) {    // browser lama: langsung ganti
    update();
    return;
  }
  document.startViewTransition(update);
}

Yang dikerjakan browser, berurutan:

  1. Memotret keadaan lama (old).
  2. Memanggil update; DOM berganti. Selama itu tampilan halaman ditahan, jadi jangan menaruh fetch yang lama di sini.
  3. Memotret keadaan baru (new).
  4. Menganimasikan dari potret lama ke baru. Default-nya crossfade 250 ms; kami ukur 250 ms di Chromium 147, WebKit 26.4, dan Firefox 146.

startViewTransition mengembalikan object ViewTransition dengan tiga Promise: updateCallbackDone (DOM sudah berganti), ready (animasinya siap mulai), finished (selesai). Ganti foto di Chromium: update di 10 ms, ready di 14 ms, finished di 275 ms. Ada juga skipTransition() untuk melewati animasinya.

Pengecekan if (!document.startViewTransition) wajib. Browser lama (Safari sebelum 18, Firefox sebelum 144) gak punya method ini; memanggilnya langsung melempar TypeError, dan DOM-nya gak pernah berganti. Dengan pengecekan, mereka tetap dapat konten baru, cuma tanpa animasi.

Pohon ::view-transition

Selama animasi, browser membuat pohon pseudo-element di atas halaman:

::view-transition
└─ ::view-transition-group(root)
   └─ ::view-transition-image-pair(root)
      ├─ ::view-transition-old(root)    potret lama, memudar keluar
      └─ ::view-transition-new(root)    potret baru, memudar masuk

root adalah seluruh halaman. Elemen yang diberi view-transition-name sendiri keluar dari potret root dan dapat cabangnya sendiri: grup yang bergeser dan berubah ukuran dari posisi lama ke baru, berisi potret lama dan barunya. Pseudo-element itu bisa kamu atur dengan CSS biasa. Ini galeri Bu Sri, geser 300 ms ease-in-out, dan total yang naik 4 px dalam 200 ms:

.foto  { view-transition-name: foto; }
.total { view-transition-name: total; }

@keyframes geser-keluar { to   { opacity: 0; transform: translateX(-24px); } }
@keyframes geser-masuk  { from { opacity: 0; transform: translateX(24px); } }
@keyframes pudar        { to   { opacity: 0; } }
@keyframes naik         { from { opacity: 0; transform: translateY(4px); } }

@media (prefers-reduced-motion: no-preference) {
  ::view-transition-old(foto)  { animation: 300ms ease-in-out both geser-keluar; }
  ::view-transition-new(foto)  { animation: 300ms ease-in-out both geser-masuk; }
  ::view-transition-old(total) { animation: 150ms ease-in both pudar; }
  ::view-transition-new(total) { animation: 200ms ease-out both naik; }
}

Satu nama, satu elemen

view-transition-name harus unik di setiap potret. Kami memberi nama foto ke foto besar dan thumbnail yang terpilih sekaligus, lalu memanggil startViewTransition:

Browser ready ditolak dengan InvalidStateError
Chromium 147 "Transition was aborted because of invalid state", plus error di console: "Unexpected duplicate view-transition-name: foto"
WebKit 26.4 "Multiple elements found with view-transition-name: foto"
Firefox 146 "Duplicate view-transition-name value while capturing old state"

Itu jawaban tebakannya: transisinya dibatalkan. Callback update tetap jalan, DOM tetap berganti, finished tetap selesai; cuma animasinya yang hilang, untuk semua elemen, bukan cuma foto.

Efek "thumbnail terbang jadi foto besar" tetap bisa: pakai nama yang sama di elemen lama (sebelum) dan elemen baru (sesudah), tapi jangan pernah di dua elemen sekaligus. Untuk daftar, beri nama berbeda per elemen (foto-1, foto-2), atau view-transition-name: match-element (Chrome 137, Safari 18.4, Firefox 144).

Antar halaman: @view-transition

Untuk navigasi biasa antar dua halaman (klik link, bukan SPA), cukup CSS di kedua halaman, dengan origin yang sama:

@view-transition {
  navigation: auto;
}

Dukungannya beda dengan versi satu halaman. Data MDN per Oktober 2026, dicocokkan dengan tes kami:

Satu halaman (startViewTransition) Antar halaman (@view-transition)
Chrome, Edge 111 126
Safari 18 18.2
Firefox 144 (Oktober 2025) belum; Firefox 146 kami membuang aturan itu dari cssRules

Kurangi gerakan

Browser gak mematikan view transition sendiri. Dengan prefers-reduced-motion: reduce, ketiga browser tetap memutar animasi bawaan 250 ms (kami ukur). Itu sebabnya geseran di atas ditaruh di no-preference, dan untuk reduce:

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*) { animation: none; }               /* gak ada gerak posisi dan ukuran */
  ::view-transition-old(*),
  ::view-transition-new(*)   { animation-duration: 150ms; }     /* sisakan fade singkat */
}

* berarti semua nama. Aturan dengan nama tertentu (foto) lebih kuat dari *, makanya geseran foto dibungkus no-preference dan gak ikut berlaku saat reduce.

⚠️ Jebakan yang sering ketemu

TL;DR: document.startViewTransition(update) memotret keadaan lama, menjalankan update (DOM berganti), memotret keadaan baru, lalu menganimasikannya (crossfade bawaan 250 ms); selalu cek if (!document.startViewTransition) dulu dan langsung panggil update sebagai fallback. Elemen dengan view-transition-name dapat cabang sendiri di pohon ::view-transition-group / -image-pair / -old / -new, yang diatur dengan CSS: galeri geser 300 ms ease-in-out, total naik 4 px 200 ms. Namanya harus unik: dua elemen bernama sama membatalkan seluruh transisi dengan InvalidStateError (DOM tetap berganti); "elemen yang sama" berarti nama yang sama di elemen sebelum dan sesudah. Satu halaman: Chrome 111, Safari 18, Firefox 144; antar halaman lewat @view-transition { navigation: auto; } di kedua halaman: Chrome 126, Safari 18.2, Firefox belum. Browser gak mengurangi gerakan sendiri: tulis ::view-transition-group(*) { animation: none } dan fade 150 ms di prefers-reduced-motion: reduce.

Yang akan kamu pelajari