Saat transisi tampilan terjadi antara dua dokumen yang berbeda, hal ini disebut transisi tampilan lintas dokumen. Hal ini biasanya terjadi pada aplikasi multi-halaman (MPA). Transisi tampilan lintas dokumen didukung di Chrome mulai Chrome 126.
Transisi tampilan lintas dokumen mengandalkan elemen penyusun dan prinsip yang sama dengan transisi tampilan dalam dokumen, yang sangat disengaja:
- Browser mengambil snapshot elemen yang memiliki
view-transition-nameunik di halaman lama dan baru. - DOM diperbarui saat rendering dihentikan.
- Terakhir, transisi didukung oleh animasi CSS.
Yang berbeda jika dibandingkan dengan transisi tampilan dalam dokumen yang sama adalah, dengan transisi tampilan lintas dokumen, Anda tidak perlu memanggil document.startViewTransition untuk memulai transisi tampilan. Sebagai gantinya, pemicu transisi tampilan lintas dokumen adalah navigasi lintas origin dari satu halaman ke halaman lain, tindakan yang biasanya dilakukan oleh pengguna situs Anda dengan mengklik link.
Dengan kata lain, tidak ada API yang dapat dipanggil untuk memulai transisi tampilan antara dua dokumen. Namun, ada dua kondisi yang harus dipenuhi:
- Kedua dokumen harus ada di origin yang sama.
- Kedua halaman harus ikut serta untuk mengizinkan transisi tampilan.
Kedua kondisi ini akan dijelaskan nanti dalam dokumen ini.
Transisi tampilan lintas dokumen terbatas pada navigasi asal yang sama
Transisi tampilan lintas dokumen hanya terbatas pada navigasi lintas origin. Navigasi dianggap memiliki origin yang sama jika origin kedua halaman yang berpartisipasi sama.
Origin halaman adalah kombinasi dari skema, nama host, dan port yang digunakan, seperti yang 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 origin yang sama.
Anda tidak dapat memiliki transisi tersebut saat bernavigasi dari developer.chrome.com ke www.chrome.com, karena keduanya adalah lintas asal dan situs yang sama.
Transisi tampilan lintas dokumen bersifat opsional
Agar transisi tampilan lintas dokumen terjadi di antara dua dokumen, kedua halaman yang berpartisipasi harus memilih untuk mengizinkannya. Hal ini dilakukan dengan aturan @@view-transition di CSS.
Dalam aturan @@view-transition, tetapkan deskriptor navigation ke auto untuk mengaktifkan transisi tampilan untuk navigasi lintas dokumen dan origin yang sama.
@view-transition {
navigation: auto;
}
Dengan menyetel deskriptor navigation ke auto, Anda memilih untuk mengizinkan transisi tampilan terjadi untuk NavigationType berikut:
traversepushataureplace, jika aktivasi tidak dimulai oleh pengguna melalui mekanisme UI browser.
Navigasi yang dikecualikan dari auto, misalnya, navigasi menggunakan kolom alamat URL atau mengklik bookmark, serta semua bentuk pemuatan ulang yang dimulai oleh pengguna atau skrip.
Jika navigasi memerlukan waktu terlalu lama–lebih dari empat detik dalam kasus Chrome–maka 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 membuka dari satu halaman ke halaman lain.
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 bersamaan dengan API tersebut.
Peristiwa pageswap dan pagereveal
Untuk memungkinkan Anda 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 dua halaman, Anda dapat mengakses objek ViewTransition menggunakan properti viewTransition pada peristiwa ini.
- Peristiwa
pageswapdiaktifkan sebelum frame terakhir halaman dirender. Anda dapat menggunakannya untuk melakukan beberapa perubahan di menit-menit terakhir pada halaman keluar, tepat sebelum snapshot lama diambil. - Peristiwa
pagerevealdiaktifkan di halaman setelah diinisialisasi atau diaktifkan kembali, tetapi sebelum peluang rendering pertama. Dengan begitu, 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 lain 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. Mereka juga menangani berbagai janji secara berbeda:
pageswap: Setelah dokumen disembunyikan, objekViewTransitionlama akan dilewati. Jika hal itu terjadi,viewTransition.readyakan menolak danviewTransition.finishedakan menyelesaikan.pagereveal: PromiseupdateCallBacksudah di-resolve pada saat ini. Anda dapat menggunakan janjiviewTransition.readydanviewTransition.finished.
Informasi aktivasi navigasi
Dalam peristiwa pageswap dan pagereveal, Anda juga dapat mengambil tindakan berdasarkan URL halaman lama dan baru.
Misalnya, di MPA Stack Navigator, jenis animasi yang akan digunakan bergantung pada jalur navigasi:
- Saat berpindah dari halaman ringkasan ke halaman detail, konten baru harus meluncur dari kanan ke kiri.
- Saat berpindah dari halaman detail ke halaman ringkasan, konten lama harus bergeser dari kiri ke kanan.
Untuk melakukannya, 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 histori tujuan saat ini, dan tujuan akhir seperti yang ditemukan di navigation.entries() dari Navigation API.
Di halaman yang diaktifkan, Anda dapat mengakses objek ini melalui navigation.activation. Dalam peristiwa pageswap, Anda dapat mengaksesnya melalui e.activation.
Lihat demo Profil ini yang menggunakan info NavigationActivation dalam 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. Sebagai gantinya, hal ini terjadi tepat waktu menggunakan JavaScript, hanya pada elemen yang memerlukannya.
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 ini juga membersihkan dirinya sendiri dengan menghapus nilai view-transition-name setelah transisi tampilan berjalan. Dengan begitu, halaman siap untuk navigasi berikutnya dan juga dapat menangani penelusuran histori.
Untuk membantu hal ini, gunakan fungsi utilitas yang menetapkan view-transition-names 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 rendering
Browser Support
Dalam beberapa kasus, Anda mungkin ingin menunda rendering pertama halaman hingga elemen tertentu ada di DOM baru. Tindakan ini menghindari kedipan dan memastikan status yang Anda animasikan stabil.
Di <head>, tentukan satu atau beberapa ID elemen yang harus ada sebelum halaman mendapatkan render pertamanya, menggunakan tag meta berikut.
<link rel="expect" blocking="render" href="#section1">
Tag meta ini berarti elemen harus ada di DOM, bukan berarti konten harus dimuat. Misalnya, dengan gambar, keberadaan tag <img> dengan id yang ditentukan di pohon DOM sudah cukup agar kondisi dievaluasi sebagai benar. Gambar itu sendiri mungkin masih dimuat.
Sebelum Anda menerapkan pemblokiran rendering sepenuhnya, perlu diketahui bahwa rendering inkremental adalah aspek mendasar dari Web, jadi berhati-hatilah saat memilih untuk memblokir rendering. Dampak pemblokiran rendering perlu dievaluasi berdasarkan kasus per kasus. Secara default, hindari penggunaan blocking=render kecuali jika Anda dapat secara aktif mengukur dan memperkirakan dampaknya terhadap pengguna, dengan mengukur dampak 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 diambil.
Misalnya, saat membuka halaman berikutnya atau halaman sebelumnya dalam penomoran halaman, Anda mungkin ingin menggunakan animasi yang berbeda, bergantung pada apakah Anda membuka halaman yang lebih tinggi atau halaman yang lebih rendah dari urutan.
Untuk menetapkan jenis ini di awal, tambahkan jenis dalam aturan @ @view-transition:
@view-transition {
navigation: auto;
types: slide, forwards;
}
Untuk menetapkan jenis secara langsung, 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 tidak otomatis ditransfer dari objek ViewTransition di halaman lama ke objek ViewTransition di halaman baru. Anda harus menentukan jenis yang akan digunakan setidaknya di halaman baru agar animasi berjalan sesuai yang diharapkan.
Untuk merespons jenis ini, gunakan pemilih pseudo-class :active-view-transition-type() dengan cara yang sama seperti pada transisi tampilan dalam dokumen
/* 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 berlaku untuk transisi tampilan aktif, jenis akan otomatis dibersihkan saat transisi tampilan selesai. Oleh karena itu, jenis berfungsi dengan baik dengan fitur seperti BFCache.
Demo
Dalam demo penomoran halaman berikut, konten halaman meluncur ke depan atau ke belakang berdasarkan nomor halaman yang Anda tuju.
Jenis transisi yang akan digunakan ditentukan dalam peristiwa pagereveal dan pageswap dengan melihat URL tujuan dan asal.
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
Masukan developer selalu kami hargai. Untuk membagikan, ajukan masalah kepada CSS Working Group di GitHub dengan saran dan pertanyaan. Awali masalah Anda dengan [css-view-transitions].
Jika Anda mengalami bug, laporkan bug Chromium.