Masalah
Situs sering kali menyediakan beberapa tampilan objek, misalnya daftar produk, lalu halaman detail untuk setiap produk. Navigasi antara kedua tampilan ini sering kali terasa terputus. Saat pengguna mengklik thumbnail produk untuk melihat detailnya, thumbnail tersebut akan menghilang dan gambar baru yang lebih besar akan langsung muncul di tempat lain di layar. Kurangnya kontinuitas ini mempersulit pengguna untuk melacak hubungan antarelemen.
Solusi
View Transitions API memungkinkan Anda menentukan pasangan elemen yang ada dalam status berbeda sebelum dan setelah transisi. Saat memicu transisi dengan document.startViewTransition() di Navigasi Halaman Tunggal (SPA), browser mengidentifikasi elemen bersama ini berdasarkan view-transition-name unik bersamanya. Kemudian, Compose akan otomatis menghitung perbedaan posisi, ukuran, dan gayanya, serta menganimasikannya dengan lancar dari status lama ke status baru. Transisi ini terjadi di lapisan atas, bahkan di atas elemen dengan nilai z-index yang tinggi.
Panduan Penerapan
Langkah 1: Gabungkan Perubahan Status di startViewTransition
Untuk Aplikasi Web Satu Halaman (SPA) atau perubahan status sederhana, gabungkan logika yang memperbarui DOM di document.startViewTransition. Browser mengambil snapshot status saat ini, menjalankan update, lalu mengambil status baru.
function navigate(view) {
// MANDATORY: Wrap the update in startViewTransition
document.startViewTransition(() => updateDOM(view));
}
Langkah 2: Tetapkan Nama Transisi Bersama
Gunakan properti CSS view-transition-name untuk memberi tahu browser elemen mana yang harus diubah. Nama dapat berupa apa saja (kecuali none). WAJIB: tidak boleh ada lebih dari 1 elemen sebelum dan sesudah dengan view-transition-name tertentu. Jika ada 2 elemen atau lebih dengan view-transition-name tertentu, DOM akan segera diperbarui ke status baru, tanpa transisi.
Anda dapat menggunakan beberapa view-transition-name untuk mengubah bentuk beberapa pasang elemen. Misalnya, Anda mungkin ingin mentransisikan gambar dan judul produk dengan transisi terpisah.
Karena ada beberapa item di tampilan daftar, Anda tidak dapat memberikan view-transition-name yang sama untuk semua item. Hal ini dapat diatasi dengan dua cara di SPA.
Halaman detail dinamis: Tetapkan
view-transition-nameunik untuk setiap item di halaman daftar, lalu terapkan nama tersebut secara dinamis ke elemen yang cocok di halaman detail saat item daftar dipilih, seperti yang ditunjukkan di sini./* In the list view, give each */ #product-1 { view-transition-name: p1 } #product-2 { view-transition-name: p2 } #product-3 { view-transition-name: p3 }function updateDOM(clickedTransitionName){ const hero = document.getElementById("hero"); hero.style.viewTransitionName = clickedTransitionName; }Item daftar dinamis: Tetapkan
view-transition-nameke elemen di halaman detail, dan terapkan nama tersebut ke item di halaman daftar saat dipilih. Hapusview-transition-namedari item di halaman daftar saat kembali ke halaman daftar.
Elemen #hero di halaman detail dan elemen .thumbnail yang dipilih di halaman daftar memiliki view-transition-name yang sama.
#hero{
view-transition-name: hero;
}
.thumbnail.selected {
view-transition-name: hero;
}
Saat thumbnail diklik, kita perlu menyiapkan tampilan daftar dengan menetapkan view-transition-name menggunakan pemilih class .selected, dan membuat perubahan apa pun pada DOM sebelum memulai transisi.
Kemudian, Anda dapat memanggil document.startViewTransition(), dan menerapkan perubahan untuk mentransisikan halaman dari tampilan detail ke tampilan daftar.
Setelah kembali ke tampilan daftar, Anda harus membersihkan class transisi tampilan untuk mencegah error pada navigasi berikutnya. Anda dapat melakukan pembersihan ini setelah promise finished transisi diselesaikan.
// Function called when a thumbnail is clicked
function goFromListToDetail(e){
e.currentTarget.classList.add("selected");
const hero = document.getElementById("hero");
const bgColor = getComputedStyle(e.currentTarget).backgroundColor;
hero.style.background = bgColor;
// Trigger the transition, checking for support
if (!document.startViewTransition) {
document.body.classList.add("detail");
// MANDATORY Accessibility Routing: Route focus to the newly revealed heading to announce context and preserve logical tab flow
document.getElementById("detail-heading")?.focus();
return; // MANDATORY: End function execution if view transitions are not supported.
}
const transition = document.startViewTransition(() => {
document.body.classList.add("detail");
});
// MANDATORY Accessibility Routing: Route focus after the view transition resolves
transition.finished.finally(() => {
document.getElementById("detail-heading")?.focus();
});
}
// Function called when navigating from detail back to list view
function goFromDetailToList() {
if (!document.startViewTransition) {
document.body.classList.remove("detail");
document.getElementById("list-heading")?.focus();
return;
}
const transition = document.startViewTransition(() => {
document.body.classList.remove("detail");
});
// Clean up the list view and route focus
transition.finished.finally(() => {
// Route focus back to list view
document.getElementById("list-heading")?.focus();
// Remove selected classList to remove view-transition-names
document.querySelectorAll(".selected").forEach(
(element) => {
element.classList.remove("selected");
},
);
});
}
Metode yang Anda pilih akan bergantung pada kasus penggunaan. Item daftar dinamis memerlukan lebih sedikit CSS berulang, tetapi memerlukan lebih banyak pembersihan JavaScript manual.
Langkah 3: Memperbaiki "Peregangan" Rasio Aspek
Secara default, browser melakukan cross-fade pada snapshot lama dan baru dalam grup yang direntangkan agar sesuai dengan keduanya. Jika Anda mentransisikan teks, tetapkan lebar elemen teks ke fit-content pada tampilan lama dan baru, sehingga rasio aspek elemen yang ditransisikan stabil.
#list-page .title {
width: fit-content;
}
#detail-page #title {
width: fit-content;
}
Jika Anda mentransisikan elemen yang mengubah rasio aspek, Anda mungkin perlu menetapkan tinggi elemen pseudo lama dan baru ke 100% dari elemen pseudo ::view-transition-pair().
::view-transition-old(hero),
::view-transition-new(hero){
height: 100%;
}
Elemen semu adalah snapshot dari elemen aktif, sehingga Anda juga dapat menggunakan deklarasi object-fit dan object-position untuk kontrol yang lebih besar atas efek transisi.
Praktik Terbaik
- JANGAN menentukan terlalu banyak transisi. Hanya gunakan elemen bersama untuk konten utama yang aktif dilacak pengguna (misalnya, banner besar, judul).
- LAKUKAN penghapusan nilai
view-transition-namesementara setelah transisi selesai untuk menghindari efek samping pada transisi mendatang. - JANGAN mentransisikan elemen dengan animasi aktif. Transisi tampilan beroperasi pada snapshot, sehingga animasi apa pun akan tampak dijeda selama transisi tampilan.
- LAKUKAN penghormatan terhadap preferensi pengguna untuk mengurangi gerakan menggunakan kueri media
prefers-reduced-motion. - Perutean Aksesibilitas WAJIB: Transisi tampilan mengubah tata letak halaman secara dinamis, tetapi tidak mengelola fokus terprogram. Jika fokus tetap berada pada elemen yang disembunyikan atau dihapus selama transisi, fokus akan ditinggalkan, sehingga pengguna keyboard dan teknologi pendukung tidak memiliki konteks. Alihkan fokus secara terprogram ke judul halaman atau penampung tampilan yang diperbarui (menggunakan
tabindex="-1") segera setelah DOM diperbarui atau saat promisefinishedtransisi tampilan diselesaikan.
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}
Strategi Penggantian
View Transitions API dirancang untuk progressive enhancement. Browser yang tidak mendukungnya akan langsung menjalankan update DOM tanpa animasi.
function navigate(){
if (!document.startViewTransition) {
// Fallback: Just update the DOM
updateDOM();
} else {
document.startViewTransition(() => updateDOM());
}
}