Transisi tampilan lintas dokumen untuk aplikasi multi-halaman

Saat terjadi transisi tampilan di antara dua dokumen yang berbeda, hal ini disebut transisi tampilan lintas dokumen. Hal ini biasanya terjadi di aplikasi multi-halaman (MPA). Transisi tampilan lintas dokumen didukung di Chrome dari Chrome 126.

Dukungan Browser

  • Chrome: 126.
  • Edge: 126.
  • Firefox: tidak didukung.
  • Safari: tidak didukung.

Transisi tampilan lintas dokumen mengandalkan elemen penyusun dan prinsip yang sama seperti transisi tampilan dokumen yang sama, yang sangat disengaja:

  1. Browser mengambil snapshot elemen yang memiliki view-transition-name unik di halaman lama dan baru.
  2. DOM diperbarui saat rendering disembunyikan.
  3. Dan akhirnya, transisi didukung 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. Sebaliknya, pemicu untuk transisi tampilan lintas dokumen adalah navigasi asal yang sama dari satu halaman ke halaman lain, tindakan yang biasanya dilakukan oleh pengguna situs Anda yang 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 asal yang sama.
  • Kedua halaman harus memilih untuk mengizinkan transisi tampilan.

Kedua kondisi ini akan dijelaskan nanti dalam dokumen ini.


Transisi tampilan lintas dokumen dibatasi untuk navigasi dari origin yang sama

Transisi tampilan lintas dokumen dibatasi hanya untuk navigasi asal yang sama. Navigasi dianggap berasal dari origin yang sama jika asal kedua halaman yang berpartisipasi sama.

Asal halaman adalah kombinasi skema, nama host, dan port yang digunakan, seperti yang diuraikan di web.dev.

Contoh URL dengan skema, nama host, dan port ditandai. Jika digabungkan, keduanya membentuk origin.
Contoh URL dengan skema, nama host, dan port ditandai. Jika digabungkan, keduanya membentuk origin.

Misalnya, Anda dapat melakukan transisi tampilan lintas dokumen saat menavigasi dari developer.chrome.com ke developer.chrome.com/blog, karena keduanya berasal dari asal yang sama. Anda tidak dapat melakukan transisi tersebut saat menavigasi dari developer.chrome.com ke www.chrome.com, karena transisi tersebut bersifat lintas origin dan situs yang sama.


Transisi tampilan lintas dokumen dapat diaktifkan

Untuk mendapatkan transisi tampilan lintas dokumen antara dua dokumen, kedua halaman yang berpartisipasi harus memilih ikut serta untuk mengizinkannya. Hal ini dilakukan dengan aturan @view-transition di CSS.

Di @view-transition sesuai aturan, tetapkan deskripsi navigation ke auto guna mengaktifkan transisi tampilan untuk navigasi dari origin yang sama dan lintas dokumen.

@view-transition {
  navigation: auto;
}

Dengan menetapkan deskripsi navigation ke auto, Anda memilih untuk mengizinkan transisi tampilan terjadi untuk NavigationType berikut:

  • traverse
  • push atau replace, jika aktivasi tidak dimulai oleh pengguna melalui mekanisme UI browser.

Navigasi yang dikecualikan dari auto, misalnya, menavigasi menggunakan kolom URL URL atau mengklik bookmark, serta segala bentuk pemuatan ulang yang dimulai oleh pengguna atau skrip.

Jika navigasi membutuhkan 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 menavigasi dari satu halaman ke halaman lainnya.

Rekaman demo Stack Navigator. Memerlukan Chrome 126 dan yang lebih baru.

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 bersama.

Peristiwa pageswap dan pagereveal

Dukungan Browser

  • Chrome: 124.
  • Edge: 124.
  • Firefox: tidak didukung.
  • Safari: tidak didukung.

Sumber

Agar Anda dapat menyesuaikan transisi tampilan lintas dokumen, spesifikasi HTML menyertakan dua peristiwa baru yang dapat Anda gunakan: pageswap dan pagereveal.

Kedua peristiwa ini akan diaktifkan untuk setiap navigasi lintas dokumen dengan 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 di peristiwa ini.

  • Peristiwa pageswap diaktifkan sebelum frame terakhir halaman dirender. Anda dapat menggunakan ini 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 fitur 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 peristiwa.

window.addEventListener("pagereveal", async (e) => {
  if (e.viewTransition) {
    if (goodReasonToSkipTheViewTransition()) {
      e.viewTransition.skipTransition();
    }
  }
}

Objek ViewTransition pada pageswap dan pagereveal adalah dua objek yang berbeda. API ini juga menangani berbagai promise dengan cara yang berbeda:

  • pageswap: Setelah dokumen disembunyikan, objek ViewTransition lama akan dilewati. Jika hal itu terjadi, viewTransition.ready akan menolak dan viewTransition.finished akan menyelesaikan.
  • pagereveal: Promise updateCallBack sudah diselesaikan pada tahap ini. Anda dapat menggunakan promise viewTransition.ready dan viewTransition.finished.

Dukungan Browser

  • Chrome: 123.
  • Edge: 123.
  • Firefox: tidak didukung.
  • Safari: tidak didukung.

Sumber

Dalam 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 masuk dari kanan ke kiri.
  • Saat menavigasi dari halaman detail ke halaman ringkasan, konten lama harus bergeser keluar dari kiri ke kanan.

Untuk melakukan hal 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 dari origin yang sama. Objek ini mengekspos jenis navigasi yang digunakan, entri histori tujuan saat ini, dan entri histori tujuan akhir seperti yang ditemukan di navigation.entries() dari Navigation API.

Pada 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 di peristiwa pageswap dan pagereveal untuk menetapkan nilai view-transition-name pada elemen yang perlu berpartisipasi dalam transisi tampilan.

Dengan demikian, Anda tidak perlu mendekorasi setiap item dalam daftar dengan view-transition-name di awal. Sebaliknya, hal ini terjadi tepat waktu dengan menggunakan JavaScript, hanya pada elemen yang membutuhkannya.

Rekaman demo Profil. Memerlukan Chrome 126 dan yang lebih baru.

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 melakukan pembersihan setelahnya dengan menghapus nilai view-transition-name setelah transisi tampilan berjalan. Dengan cara ini, halaman siap untuk navigasi berurutan dan juga dapat menangani traversal histori.

Untuk membantu hal ini, gunakan fungsi utilitas ini 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

Dukungan Browser

  • Chrome: 124.
  • Edge: 124.
  • Firefox: tidak didukung.
  • Safari: tidak didukung.

Dalam beberapa kasus, Anda mungkin perlu menahan render pertama halaman hingga elemen tertentu ditempatkan di DOM baru. Hal ini akan menghindari flash 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 bahwa elemen harus ada di DOM, bukan konten harus dimuat. Misalnya, dengan gambar, keberadaan tag <img> dengan id yang ditentukan dalam hierarki DOM sudah cukup bagi kondisi untuk dievaluasi ke benar. Gambar itu sendiri masih dapat dimuat.

Sebelum Anda melakukan pemblokiran render secara menyeluruh, perhatikan bahwa rendering inkremental merupakan aspek dasar Web, jadi berhati-hatilah saat memilih untuk memblokir rendering. Dampak dari rendering pemblokiran perlu dievaluasi berdasarkan 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 Core Web Vitals.


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 menuju ke halaman berikutnya atau sebelumnya dalam penomoran halaman, Anda mungkin ingin menggunakan animasi yang berbeda, bergantung pada apakah Anda akan membuka halaman yang lebih tinggi atau halaman yang lebih rendah dari urutan tersebut.

Untuk menetapkan jenis ini di awal, tambahkan jenis dalam @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 ini tidak secara otomatis dipindahkan dari objek ViewTransition di halaman lama ke objek ViewTransition di halaman baru. Anda perlu menentukan jenis yang akan digunakan setidaknya pada halaman baru agar animasi dapat berjalan seperti yang diharapkan.

Untuk merespons jenis ini, gunakan pemilih class semu :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 berlaku untuk transisi tampilan aktif, jenis akan dibersihkan secara otomatis saat transisi tampilan selesai. Karena itu, jenis huruf berfungsi baik dengan fitur seperti BFCache.

Demo

Pada demo penomoran halaman berikut, konten halaman bergeser maju atau mundur berdasarkan nomor halaman yang Anda buka.

Rekaman demo penomoran halaman (MPA). Fungsi ini menggunakan transisi yang berbeda, bergantung pada halaman yang akan Anda buka.

Jenis transisi yang akan digunakan ditentukan dalam peristiwa pagereveal dan pageswap dengan melihat URL ke dan dari.

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 membagikannya, ajukan masalah kepada CSS Working Group di GitHub beserta saran dan pertanyaan. Awali masalah Anda dengan [css-view-transitions]. Jika Anda mengalami bug, laporkan bug Chromium.