Saat terjadi transisi tampilan di antara dua dokumen yang berbeda, transisi ini disebut transisi tampilan lintas dokumen. Hal ini biasanya terjadi pada aplikasi multi-halaman (MPA). Transisi tampilan lintas dokumen didukung di Chrome dari Chrome 126.
Transisi tampilan lintas dokumen mengandalkan elemen dan prinsip yang sama seperti transisi tampilan dokumen yang sama, yang sangat disengaja:
- Browser mengambil snapshot elemen yang memiliki
view-transition-name
unik di halaman lama dan baru. - DOM diperbarui saat rendering ditekan.
- Dan terakhir, transisi ditenagai oleh animasi CSS.
Yang berbeda jika dibandingkan dengan transisi tampilan dokumen yang sama, adalah dengan transisi tampilan lintas dokumen, Anda tidak perlu memanggil document.startViewTransition
untuk memulai transisi tampilan. Sebagai gantinya, pemicu untuk transisi tampilan lintas dokumen adalah navigasi asal yang sama dari satu halaman ke halaman lainnya, tindakan yang biasanya dilakukan oleh pengguna situs Anda yang mengklik link.
Dengan kata lain, tidak ada API yang harus dipanggil untuk memulai transisi tampilan antara dua dokumen. Namun, ada dua kondisi yang perlu dipenuhi:
- Kedua dokumen harus ada di asal yang sama.
- Kedua halaman harus memilih ikut serta untuk memungkinkan transisi tampilan.
Kedua kondisi ini akan dijelaskan nanti dalam dokumen ini.
Transisi tampilan lintas dokumen dibatasi untuk navigasi asal yang sama
Transisi tampilan lintas dokumen dibatasi hanya untuk navigasi asal yang sama. Navigasi dianggap memiliki origin yang sama jika origin kedua halaman yang berpartisipasi sama.
Asal halaman adalah kombinasi dari skema, nama host, dan port yang digunakan, sebagaimana dijelaskan di web.dev.
Misalnya, Anda dapat memiliki transisi tampilan lintas dokumen saat menavigasi dari developer.chrome.com
ke developer.chrome.com/blog
, karena keduanya memiliki asal yang sama.
Anda tidak dapat memiliki transisi tersebut saat menavigasi dari developer.chrome.com
ke www.chrome.com
, karena transisi tersebut adalah lintas asal dan situs yang sama.
Transisi tampilan lintas dokumen diaktifkan
Agar dapat melakukan transisi tampilan lintas dokumen antara dua dokumen, kedua halaman yang berpartisipasi harus memilih ikut serta untuk mengizinkan hal ini. Hal ini dilakukan dengan @view-transition
at-rule di CSS.
Pada aturan @view-transition
, tetapkan deskripsi navigation
ke auto
guna mengaktifkan transisi tampilan untuk navigasi lintas dokumen dari origin yang sama.
@view-transition {
navigation: auto;
}
Dengan menetapkan deskripsi navigation
ke auto
, Anda memilih untuk mengizinkan transisi tampilan terjadi untuk NavigationType berikut:
traverse
push
ataureplace
, jika aktivasi tidak dimulai oleh pengguna melalui mekanisme UI browser.
Navigasi yang dikecualikan dari auto
adalah, bernavigasi menggunakan kolom URL URL atau mengklik bookmark, serta segala bentuk pemuatan ulang skrip atau pengguna atau pengguna.
Jika navigasi memakan waktu terlalu lama–lebih dari empat detik dalam kasus Chrome–transisi tampilan akan dilewati dengan TimeoutError
DOMException
.
Demo transisi tampilan lintas dokumen
Lihat demo berikut yang menggunakan transisi tampilan untuk membuat demo Stack Navigator. Tidak ada panggilan ke document.startViewTransition()
di sini, transisi tampilan dipicu dengan berpindah dari satu halaman ke halaman lainnya.
Menyesuaikan transisi tampilan lintas dokumen
Untuk menyesuaikan transisi tampilan lintas dokumen, ada beberapa fitur platform web yang dapat Anda gunakan.
Fitur ini bukan bagian dari spesifikasi View Transition API itu sendiri, tetapi dirancang untuk digunakan bersamanya.
Peristiwa pageswap
dan pagereveal
Agar Anda dapat menyesuaikan transisi tampilan lintas dokumen, spesifikasi HTML menyertakan dua peristiwa baru yang dapat Anda gunakan: pageswap
dan pagereveal
.
Kedua peristiwa ini diaktifkan untuk setiap navigasi lintas dokumen origin yang sama, terlepas dari apakah transisi tampilan akan terjadi atau tidak. Jika transisi tampilan akan terjadi di antara kedua halaman, Anda dapat mengakses objek ViewTransition
menggunakan properti viewTransition
pada peristiwa tersebut.
- Peristiwa
pageswap
aktif sebelum frame terakhir halaman dirender. Anda dapat menggunakannya untuk melakukan beberapa perubahan mendadak pada halaman keluar, tepat sebelum snapshot lama diambil. - Peristiwa
pagereveal
diaktifkan di halaman setelah diinisialisasi atau diaktifkan kembali, tetapi sebelum peluang rendering pertama. Dengan ID ini, Anda dapat menyesuaikan halaman baru sebelum snapshot baru diambil.
Misalnya, Anda dapat menggunakan peristiwa ini untuk menetapkan atau mengubah beberapa nilai view-transition-name
dengan cepat, atau meneruskan data dari satu dokumen ke dokumen lainnya dengan menulis dan membaca data dari sessionStorage
untuk menyesuaikan transisi tampilan sebelum benar-benar berjalan.
let lastClickX, lastClickY;
document.addEventListener('click', (event) => {
if (event.target.tagName.toLowerCase() === 'a') return;
lastClickX = event.clientX;
lastClickY = event.clientY;
});
// Write position to storage on old page
window.addEventListener('pageswap', (event) => {
if (event.viewTransition && lastClick) {
sessionStorage.setItem('lastClickX', lastClickX);
sessionStorage.setItem('lastClickY', lastClickY);
}
});
// Read position from storage on new page
window.addEventListener('pagereveal', (event) => {
if (event.viewTransition) {
lastClickX = sessionStorage.getItem('lastClickX');
lastClickY = sessionStorage.getItem('lastClickY');
}
});
Jika mau, Anda dapat memutuskan untuk melewati transisi di kedua acara.
window.addEventListener("pagereveal", async (e) => {
if (e.viewTransition) {
if (goodReasonToSkipTheViewTransition()) {
e.viewTransition.skipTransition();
}
}
}
Objek ViewTransition
di pageswap
dan pagereveal
adalah dua objek yang berbeda. Fungsi tersebut juga menangani berbagai promise dengan cara yang berbeda:
pageswap
: Setelah dokumen disembunyikan, objekViewTransition
lama akan dilewati. Jika hal itu terjadi,viewTransition.ready
akan menolak danviewTransition.finished
akan me-resolve.pagereveal
: PromiseupdateCallBack
telah diselesaikan pada tahap ini. Anda dapat menggunakan promiseviewTransition.ready
danviewTransition.finished
.
Informasi aktivasi navigasi
Di peristiwa pageswap
dan pagereveal
, Anda juga dapat mengambil tindakan berdasarkan URL halaman lama dan baru.
Misalnya, dalam MPA Stack Navigator, jenis animasi yang akan digunakan bergantung pada jalur navigasi:
- Saat menavigasi dari halaman ringkasan ke halaman detail, konten baru harus bergeser dari kanan ke kiri.
- Saat membuka halaman detail ke halaman ringkasan, konten lama harus digeser dari kiri ke kanan.
Untuk melakukan ini, Anda memerlukan informasi tentang navigasi yang, dalam kasus pageswap
, akan terjadi atau, dalam kasus pagereveal
baru saja terjadi.
Untuk itu, browser kini dapat mengekspos objek NavigationActivation
yang menyimpan info tentang navigasi origin yang sama. Objek ini mengekspos jenis navigasi yang digunakan, entri saat ini, dan entri histori tujuan akhir seperti yang ditemukan di navigation.entries()
dari Navigation API.
Di halaman yang diaktifkan, Anda dapat mengakses objek ini melalui navigation.activation
. Di peristiwa pageswap
, Anda dapat mengaksesnya melalui e.activation
.
Lihat Demo profil ini yang menggunakan info NavigationActivation
di peristiwa pageswap
dan pagereveal
untuk menetapkan nilai view-transition-name
pada elemen yang perlu berpartisipasi dalam transisi tampilan.
Dengan begitu, Anda tidak perlu mendekorasi setiap item dalam daftar dengan view-transition-name
di awal. Sebaliknya, ini terjadi tepat waktu menggunakan JavaScript, hanya pada elemen yang membutuhkannya.
Kodenya adalah sebagai berikut ini:
// OLD PAGE LOGIC
window.addEventListener('pageswap', async (e) => {
if (e.viewTransition) {
const targetUrl = new URL(e.activation.entry.url);
// Navigating to a profile page
if (isProfilePage(targetUrl)) {
const profile = extractProfileNameFromUrl(targetUrl);
// Set view-transition-name values on the clicked row
document.querySelector(`#${profile} span`).style.viewTransitionName = 'name';
document.querySelector(`#${profile} img`).style.viewTransitionName = 'avatar';
// Remove view-transition-names after snapshots have been taken
// (this to deal with BFCache)
await e.viewTransition.finished;
document.querySelector(`#${profile} span`).style.viewTransitionName = 'none';
document.querySelector(`#${profile} img`).style.viewTransitionName = 'none';
}
}
});
// NEW PAGE LOGIC
window.addEventListener('pagereveal', async (e) => {
if (e.viewTransition) {
const fromURL = new URL(navigation.activation.from.url);
const currentURL = new URL(navigation.activation.entry.url);
// Navigating from a profile page back to the homepage
if (isProfilePage(fromURL) && isHomePage(currentURL)) {
const profile = extractProfileNameFromUrl(currentURL);
// Set view-transition-name values on the elements in the list
document.querySelector(`#${profile} span`).style.viewTransitionName = 'name';
document.querySelector(`#${profile} img`).style.viewTransitionName = 'avatar';
// Remove names after snapshots have been taken
// so that we're ready for the next navigation
await e.viewTransition.ready;
document.querySelector(`#${profile} span`).style.viewTransitionName = 'none';
document.querySelector(`#${profile} img`).style.viewTransitionName = 'none';
}
}
});
Kode tersebut juga membersihkan setelah itu sendiri dengan menghapus nilai view-transition-name
setelah transisi tampilan berjalan. Dengan cara ini, halaman siap untuk navigasi berturut-turut dan juga dapat menangani traversal histori.
Untuk membantu melakukan hal ini, gunakan fungsi utilitas yang menetapkan view-transition-name
untuk sementara.
const setTemporaryViewTransitionNames = async (entries, vtPromise) => {
for (const [$el, name] of entries) {
$el.style.viewTransitionName = name;
}
await vtPromise;
for (const [$el, name] of entries) {
$el.style.viewTransitionName = '';
}
}
Kode sebelumnya kini dapat disederhanakan sebagai berikut:
// OLD PAGE LOGIC
window.addEventListener('pageswap', async (e) => {
if (e.viewTransition) {
const targetUrl = new URL(e.activation.entry.url);
// Navigating to a profile page
if (isProfilePage(targetUrl)) {
const profile = extractProfileNameFromUrl(targetUrl);
// Set view-transition-name values on the clicked row
// Clean up after the page got replaced
setTemporaryViewTransitionNames([
[document.querySelector(`#${profile} span`), 'name'],
[document.querySelector(`#${profile} img`), 'avatar'],
], e.viewTransition.finished);
}
}
});
// NEW PAGE LOGIC
window.addEventListener('pagereveal', async (e) => {
if (e.viewTransition) {
const fromURL = new URL(navigation.activation.from.url);
const currentURL = new URL(navigation.activation.entry.url);
// Navigating from a profile page back to the homepage
if (isProfilePage(fromURL) && isHomePage(currentURL)) {
const profile = extractProfileNameFromUrl(currentURL);
// Set view-transition-name values on the elements in the list
// Clean up after the snapshots have been taken
setTemporaryViewTransitionNames([
[document.querySelector(`#${profile} span`), 'name'],
[document.querySelector(`#${profile} img`), 'avatar'],
], e.viewTransition.ready);
}
}
});
Menunggu konten dimuat dengan pemblokiran render
Dalam beberapa kasus, Anda mungkin ingin menahan render pertama halaman sampai elemen tertentu muncul di DOM baru. Hal ini akan menghindari flash dan memastikan status yang Anda animasikan stabil.
Di <head>
, tentukan satu atau beberapa ID elemen yang perlu ada sebelum halaman mendapatkan render pertamanya, menggunakan tag meta berikut.
<link rel="expect" blocking="render" href="#section1">
Tag meta ini berarti bahwa elemen harus ada di DOM, bukan konten yang harus dimuat. Misalnya pada gambar, keberadaan tag <img>
dengan id
yang ditentukan dalam hierarki DOM sudah cukup bagi kondisi untuk dievaluasi ke benar (true). Gambar itu sendiri masih dapat dimuat.
Sebelum Anda mempelajari lebih lanjut tentang pemblokiran render, perhatikan bahwa rendering inkremental adalah aspek dasar Web, jadi berhati-hatilah saat memilih untuk memblokir rendering. Dampak rendering yang memblokir perlu dievaluasi secara kasus per kasus. Secara default, hindari penggunaan blocking=render
kecuali jika Anda dapat secara aktif mengukur dan mengukur dampaknya terhadap pengguna, dengan mengukur dampaknya terhadap Data Web Inti Anda.
Melihat jenis transisi dalam transisi tampilan lintas dokumen
Transisi tampilan lintas dokumen juga mendukung jenis transisi tampilan untuk menyesuaikan animasi dan elemen mana yang akan diambil.
Misalnya, saat membuka halaman berikutnya atau sebelumnya dalam penomoran halaman, Anda dapat menggunakan animasi yang berbeda bergantung pada apakah Anda akan membuka halaman yang lebih tinggi atau halaman yang lebih rendah dari urutan.
Untuk menetapkan jenis ini di awal, tambahkan jenis di @view-transition
at-rule:
@view-transition {
navigation: auto;
types: slide, forwards;
}
Untuk menetapkan jenis dengan cepat, gunakan peristiwa pageswap
dan pagereveal
untuk memanipulasi nilai e.viewTransition.types
.
window.addEventListener("pagereveal", async (e) => {
if (e.viewTransition) {
const transitionType = determineTransitionType(navigation.activation.from, navigation.activation.entry);
e.viewTransition.types.add(transitionType);
}
});
Jenis tersebut tidak otomatis dipindahkan dari objek ViewTransition
di halaman lama ke objek ViewTransition
di halaman baru. Anda perlu menentukan jenis yang akan digunakan setidaknya di halaman baru agar animasi dapat berjalan seperti yang diharapkan.
Untuk merespons jenis ini, gunakan pemilih class pseudo :active-view-transition-type()
dengan cara yang sama seperti transisi tampilan dokumen yang sama
/* Determine what gets captured when the type is forwards or backwards */
html:active-view-transition-type(forwards, backwards) {
:root {
view-transition-name: none;
}
article {
view-transition-name: content;
}
.pagination {
view-transition-name: pagination;
}
}
/* Animation styles for forwards type only */
html:active-view-transition-type(forwards) {
&::view-transition-old(content) {
animation-name: slide-out-to-left;
}
&::view-transition-new(content) {
animation-name: slide-in-from-right;
}
}
/* Animation styles for backwards type only */
html:active-view-transition-type(backwards) {
&::view-transition-old(content) {
animation-name: slide-out-to-right;
}
&::view-transition-new(content) {
animation-name: slide-in-from-left;
}
}
/* Animation styles for reload type only */
html:active-view-transition-type(reload) {
&::view-transition-old(root) {
animation-name: fade-out, scale-down;
}
&::view-transition-new(root) {
animation-delay: 0.25s;
animation-name: fade-in, scale-up;
}
}
Karena jenis hanya diterapkan pada transisi tampilan aktif, jenis akan dibersihkan secara otomatis saat transisi tampilan selesai. Karena itu, jenis dapat berfungsi baik dengan fitur seperti BFCache.
Demo
Pada demo penomoran halaman berikut, konten halaman bergeser maju atau mundur berdasarkan nomor halaman yang Anda buka.
Jenis transisi yang akan digunakan ditentukan dalam peristiwa pagereveal
dan pageswap
dengan melihat ke dan dari URL.
const determineTransitionType = (fromNavigationEntry, toNavigationEntry) => {
const currentURL = new URL(fromNavigationEntry.url);
const destinationURL = new URL(toNavigationEntry.url);
const currentPathname = currentURL.pathname;
const destinationPathname = destinationURL.pathname;
if (currentPathname === destinationPathname) {
return "reload";
} else {
const currentPageIndex = extractPageIndexFromPath(currentPathname);
const destinationPageIndex = extractPageIndexFromPath(destinationPathname);
if (currentPageIndex > destinationPageIndex) {
return 'backwards';
}
if (currentPageIndex < destinationPageIndex) {
return 'forwards';
}
return 'unknown';
}
};
Masukan
Kami selalu mengharapkan masukan dari developer. Untuk menyampaikannya, ajukan masalah ke Kelompok Kerja CSS di GitHub beserta saran dan pertanyaan. Awali masalah Anda dengan [css-view-transitions]
.
Jika Anda mengalami bug, sebaiknya laporkan bug Chromium.