การเปลี่ยนมุมมองข้ามเอกสารสำหรับแอปพลิเคชันที่มีหลายหน้า

เมื่อเกิดการเปลี่ยนมุมมองระหว่างเอกสาร 2 ฉบับที่ต่างกัน จะเรียกว่าการเปลี่ยนมุมมองข้ามเอกสาร ซึ่งมักเกิดขึ้นในแอปพลิเคชันที่มีหลายหน้า (MPA) การเปลี่ยนมุมมองข้ามเอกสารใช้ได้ใน Chrome จาก Chrome 126

การสนับสนุนเบราว์เซอร์

  • 126
  • 126
  • x
  • x

แหล่งที่มา

การเปลี่ยนมุมมองข้ามเอกสารใช้องค์ประกอบพื้นฐานและหลักการเดียวกันกับการเปลี่ยนผ่านการดูเอกสารเดียวกัน ซึ่งเป็นสิ่งที่ตั้งใจอย่างมาก ดังนี้

  1. เบราว์เซอร์จะบันทึกสแนปชอตขององค์ประกอบที่มี view-transition-name ที่ไม่ซ้ำกันทั้งในหน้าเก่าและหน้าใหม่
  2. DOM ได้รับการอัปเดตขณะระงับการแสดงผล
  3. และสุดท้าย การเปลี่ยนขับเคลื่อนโดยภาพเคลื่อนไหว CSS

สิ่งที่แตกต่างเมื่อเทียบกับการเปลี่ยนมุมมองเอกสารเดียวกันคือ เมื่อใช้การเปลี่ยนมุมมองข้ามเอกสาร คุณไม่จำเป็นต้องเรียกใช้ document.startViewTransition เพื่อเริ่มการเปลี่ยนมุมมอง แต่ทริกเกอร์สําหรับการเปลี่ยนมุมมองข้ามเอกสารคือการนําทางต้นทางเดียวกันจากหน้าหนึ่งไปยังอีกหน้าหนึ่ง ซึ่งเป็นการดําเนินการที่ผู้ใช้เว็บไซต์คลิกลิงก์ตามปกติ

กล่าวคือ ไม่มี API ให้เรียกใช้เพื่อเริ่มต้นการเปลี่ยนมุมมองระหว่างเอกสาร 2 เอกสาร อย่างไรก็ตาม มีเงื่อนไข 2 ประการที่ต้องปฏิบัติตาม นั่นคือ

  • เอกสารทั้งสองจะต้องอยู่ในต้นทางเดียวกัน
  • โดยทั้ง 2 หน้าจะต้องเลือกใช้เพื่ออนุญาตให้เปลี่ยนมุมมอง

เงื่อนไขทั้งสองนี้มีการอธิบายไว้ภายหลังในเอกสารนี้


การเปลี่ยนมุมมองข้ามเอกสารจํากัดไว้สําหรับการนําทางต้นทางเดียวกันเท่านั้น

การเปลี่ยนมุมมองข้ามเอกสารจํากัดไว้สำหรับการนําทางต้นทางเดียวกันเท่านั้น ระบบจะถือว่าการนำทางมีต้นทางเดียวกัน หากต้นทางของหน้าที่เข้าร่วมทั้ง 2 หน้าเหมือนกัน

ต้นทางของหน้าเว็บคือชุดค่าผสมของรูปแบบ ชื่อโฮสต์ และพอร์ตที่ใช้ ตามที่รายละเอียดใน web.dev

URL ตัวอย่างที่ไฮไลต์สคีม ชื่อโฮสต์ และพอร์ต เมื่อรวมกันก็จะเป็นต้นกำเนิด
ตัวอย่าง URL ที่ไฮไลต์รูปแบบ ชื่อโฮสต์ และพอร์ต เมื่อรวมกันก็จะเป็นต้นกำเนิด

เช่น คุณเปลี่ยนมุมมองข้ามเอกสารได้เมื่อนำทางจาก developer.chrome.com ไปยัง developer.chrome.com/blog เพราะเป็นต้นทางเดียวกัน คุณเปลี่ยนแบบนั้นไม่ได้เมื่อนำทางจาก developer.chrome.com ไปยัง www.chrome.com เนื่องจากเป็นแบบข้ามต้นทางและในเว็บไซต์เดียวกัน


เลือกใช้การเปลี่ยนมุมมองข้ามเอกสาร

หากต้องการเปลี่ยนมุมมองข้ามเอกสารระหว่างเอกสาร 2 ฉบับ หน้าที่เข้าร่วมทั้ง 2 หน้าจะต้องเลือกใช้การอนุญาตนี้ ซึ่งทำได้ด้วยกฎกลาง @view-transition ใน CSS

ในกฎแอตทริบิวต์ @view-transition ให้ตั้งค่าข้อบ่งชี้ navigation เป็น auto เพื่อเปิดใช้การเปลี่ยนมุมมองสำหรับการนำทางแบบข้ามเอกสารที่มีต้นทางเดียวกัน

@view-transition {
  navigation: auto;
}

เมื่อตั้งค่าข้อบ่งชี้ navigation เป็น auto หมายความว่าคุณเลือกอนุญาตให้เปลี่ยนมุมมองสำหรับ NavigationType ต่อไปนี้

  • traverse
  • push หรือ replace หากผู้ใช้ไม่ได้เป็นผู้เริ่มเปิดใช้งานผ่านกลไก UI ของเบราว์เซอร์

ตัวอย่างเช่น การนำทางที่ยกเว้นจาก auto เช่น การนำทางโดยใช้แถบที่อยู่ของ URL หรือการคลิกบุ๊กมาร์ก รวมถึงการโหลดซ้ำของผู้ใช้หรือสคริปต์ในรูปแบบใดๆ

หากการนำทางใช้เวลานานกว่า 4 วินาทีในกรณีของ Chrome ระบบจะข้ามการเปลี่ยนมุมมองด้วย TimeoutError DOMException

การสาธิตการเปลี่ยนมุมมองข้ามเอกสาร

ลองดูการสาธิตต่อไปนี้ซึ่งใช้การเปลี่ยนมุมมองเพื่อสร้างการสาธิต Stack Navigator ไม่มีการเรียกไปที่ document.startViewTransition() ที่นี่ การเปลี่ยนมุมมองจะเกิดขึ้นโดยการไปยังส่วนต่างๆ จากหน้าหนึ่งไปอีกหน้าหนึ่ง

การบันทึกการสาธิต Stack Navigator ต้องใช้ Chrome 126 ขึ้นไป

ปรับแต่งการเปลี่ยนมุมมองข้ามเอกสาร

คุณใช้ฟีเจอร์แพลตฟอร์มเว็บบางรายการเพื่อปรับแต่งการเปลี่ยนมุมมองข้ามเอกสารได้

ฟีเจอร์เหล่านี้ไม่ได้เป็นส่วนหนึ่งของข้อกำหนดของ View Transition API แต่ออกแบบมาให้ใช้ร่วมกับฟีเจอร์ดังกล่าว

เหตุการณ์ pageswap และ pagereveal

การสนับสนุนเบราว์เซอร์

  • 124
  • 124
  • x
  • x

แหล่งที่มา

เพื่อให้คุณปรับแต่งการเปลี่ยนมุมมองข้ามเอกสารได้ ข้อกำหนด HTML ได้รวมเหตุการณ์ใหม่ 2 เหตุการณ์ที่คุณสามารถใช้ได้ ได้แก่ pageswap และ pagereveal

เหตุการณ์ 2 รายการนี้จะเริ่มทํางานสําหรับการไปยังส่วนต่างๆ แบบข้ามเอกสารต้นทางเดียวกันทั้งหมดโดยไม่คํานึงว่าการเปลี่ยนมุมมองจะเกิดขึ้นหรือไม่ หากกำลังจะเกิดการเปลี่ยนมุมมองระหว่าง 2 หน้า คุณจะเข้าถึงออบเจ็กต์ ViewTransition ได้โดยใช้พร็อพเพอร์ตี้ viewTransition ในเหตุการณ์เหล่านี้

  • เหตุการณ์ pageswap จะเริ่มทำงานก่อนแสดงผลเฟรมสุดท้ายของหน้าเว็บ คุณสามารถใช้วิธีนี้เพื่อทำการเปลี่ยนแปลงในนาทีสุดท้ายในหน้าขาออกก่อนที่ระบบจะบันทึกสแนปชอตเก่า
  • เหตุการณ์ pagereveal จะเริ่มทำงานในหน้าเว็บหลังจากที่เริ่มต้นหรือเปิดใช้งานอีกครั้ง แต่ก่อนโอกาสในการแสดงผลครั้งแรก ซึ่งจะช่วยให้คุณปรับแต่งหน้าเว็บใหม่ได้ก่อนที่ระบบจะบันทึกสแนปชอตใหม่

ตัวอย่างเช่น คุณสามารถใช้เหตุการณ์เหล่านี้เพื่อตั้งค่าหรือเปลี่ยนค่า view-transition-name บางค่าอย่างรวดเร็ว หรือส่งผ่านข้อมูลจากเอกสารหนึ่งไปยังอีกเอกสารหนึ่งได้อย่างรวดเร็วโดยการเขียนและอ่านข้อมูลจาก sessionStorage เพื่อปรับแต่งการเปลี่ยนมุมมองก่อนที่จะทำงานจริง

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');
  }
});

หากต้องการ คุณสามารถเลือกข้ามการเปลี่ยนในทั้ง 2 เหตุการณ์ได้

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

ออบเจ็กต์ ViewTransition ใน pageswap และ pagereveal เป็นออบเจ็กต์ 2 รายการที่ต่างกัน นอกจากนี้ ยังจัดการกับคำสัญญาต่างๆ แตกต่างออกไป ดังนี้

  • pageswap: เมื่อซ่อนเอกสารแล้ว ระบบจะข้ามออบเจ็กต์ ViewTransition เก่า ในกรณีนี้ viewTransition.ready จะปฏิเสธและ viewTransition.finished แก้ปัญหา
  • pagereveal: สัญญา updateCallBack ได้รับการแก้ไขแล้วในตอนนี้ คุณใช้คำสัญญา viewTransition.ready และ viewTransition.finished ได้

การสนับสนุนเบราว์เซอร์

  • 123
  • 123
  • x
  • x

แหล่งที่มา

ในเหตุการณ์ pageswap และ pagereveal คุณสามารถดำเนินการตาม URL ของหน้าเก่าและใหม่ได้เช่นกัน

ตัวอย่างเช่น ใน MPA Stack Navigator ประเภทภาพเคลื่อนไหวที่จะใช้จะขึ้นอยู่กับเส้นทางการนำทาง ดังนี้

  • เมื่อนำทางจากหน้าภาพรวมไปยังหน้ารายละเอียด เนื้อหาใหม่จะต้องเลื่อนจากด้านขวาไปด้านซ้าย
  • เมื่อนำทางจากหน้ารายละเอียดไปยังหน้าภาพรวม เนื้อหาเก่าจะต้องเลื่อนจากซ้ายไปขวา

คุณต้องการข้อมูลเกี่ยวกับการนําทางที่จะเกิดขึ้นในกรณีของ pageswap หรือในกรณีของ pagereveal เพิ่งเกิดขึ้น

สำหรับกรณีนี้ เบราว์เซอร์จะแสดงออบเจ็กต์ NavigationActivation รายการซึ่งเก็บข้อมูลเกี่ยวกับการนำทางต้นทางเดียวกันได้ ออบเจ็กต์นี้จะแสดงประเภทการนำทางที่ใช้ รายการประวัติปลายทางปัจจุบัน และรายการประวัติปลายทางสุดท้ายตามที่พบใน navigation.entries() จาก Navigation API

ในหน้าที่เปิดใช้งาน คุณจะเข้าถึงออบเจ็กต์นี้ได้ผ่าน navigation.activation ในกิจกรรม pageswap คุณจะเข้าถึงข้อมูลนี้ได้ผ่าน e.activation

โปรดดูการสาธิตโปรไฟล์นี้ที่ใช้ข้อมูล NavigationActivation ในกิจกรรม pageswap และ pagereveal เพื่อกำหนดค่า view-transition-name ในองค์ประกอบที่ต้องเข้าร่วมการเปลี่ยนมุมมอง

วิธีนี้ช่วยให้คุณไม่ต้องตกแต่งสินค้าแต่ละรายการในลิสต์ด้วย view-transition-name ล่วงหน้า แต่จะดำเนินการให้ทันท่วงทีโดยใช้ JavaScript เฉพาะในองค์ประกอบที่จำเป็นต้องใช้เท่านั้น

การบันทึกการสาธิตโปรไฟล์ ต้องใช้ Chrome 126 ขึ้นไป

โค้ดมีดังนี้

// 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';
    }
  }
});

นอกจากนี้โค้ดจะล้างค่าเองโดยนำค่า view-transition-name ออกหลังจากที่เริ่มการเปลี่ยนมุมมอง วิธีนี้ทำให้หน้าพร้อมสำหรับการไปยังส่วนต่างๆ แบบต่อเนื่องและสามารถจัดการกับการข้ามผ่านประวัติการเข้าชมได้ด้วย

ถ้าจะให้ช่วยในเรื่องนี้ ให้ใช้ฟังก์ชันยูทิลิตีนี้ที่ตั้ง view-transition-name ชั่วคราว

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 = '';
  }
}

ลดความซับซ้อนของโค้ดก่อนหน้านี้ได้ดังนี้

// 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);
    }
  }
});

รอให้เนื้อหาโหลดพร้อมการบล็อกการแสดงผล

การสนับสนุนเบราว์เซอร์

  • 124
  • 124
  • x
  • x

แหล่งที่มา

ในบางกรณี คุณอาจต้องระงับการแสดงผลครั้งแรกของหน้าเว็บไว้จนกว่าองค์ประกอบบางอย่างจะปรากฏใน DOM ใหม่ วิธีนี้จะหลีกเลี่ยงการกะพริบและทำให้แน่ใจว่าสถานะที่คุณกำลังเคลื่อนไหวนั้นมีความเสถียร

ใน <head> ให้กำหนดรหัสองค์ประกอบอย่างน้อย 1 รายการที่ต้องแสดงก่อนที่หน้าเว็บจะได้รับการแสดงผลครั้งแรก โดยใช้เมตาแท็กต่อไปนี้

<link rel="expect" blocking="render" href="#section1">

เมตาแท็กนี้หมายความว่าควรมีองค์ประกอบใน DOM ไม่ใช่ควรโหลดเนื้อหา ตัวอย่างเช่น เมื่อมีรูปภาพ เพียงแท็ก <img> ที่มี id ที่ระบุในแผนผัง DOM ก็เพียงพอให้เงื่อนไขประเมินเป็น "จริง" แล้ว ตัวรูปภาพจะยังโหลดอยู่

ก่อนที่จะดำเนินการบล็อกการแสดงผลแบบเต็มรูปแบบ โปรดทราบว่าการแสดงผลที่เพิ่มขึ้นเป็นแง่มุมพื้นฐานของเว็บ ดังนั้นโปรดระวังเมื่อเลือกที่จะบล็อกการแสดงผล เราจะประเมินผลของการบล็อกการแสดงผลเป็นกรณีๆ ไป โดยค่าเริ่มต้น ให้หลีกเลี่ยงการใช้ blocking=render เว้นแต่คุณจะวัดผลและวัดผลกระทบที่มีต่อผู้ใช้ได้อย่างสม่ำเสมอด้วยการวัดผลกระทบต่อ Core Web Vitals


ดูประเภทการเปลี่ยนในการเปลี่ยนมุมมองข้ามเอกสาร

การเปลี่ยนมุมมองข้ามเอกสารยังรองรับประเภทการเปลี่ยนแบบดูเพื่อปรับแต่งภาพเคลื่อนไหวและองค์ประกอบที่จะจับภาพด้วย

เช่น เมื่อไปที่หน้าถัดไปหรือหน้าก่อนหน้าในแบบการใส่เลขหน้า คุณอาจต้องการใช้ภาพเคลื่อนไหวที่แตกต่างกัน โดยขึ้นอยู่กับว่าคุณจะไปที่หน้าที่สูงขึ้นหรือหน้าก่อนหน้าจากลําดับล่าง

หากต้องการตั้งค่าประเภทเหล่านี้ล่วงหน้า ให้เพิ่มประเภทในกฎ @view-transition ดังนี้

@view-transition {
  navigation: auto;
  types: slide, forwards;
}

หากต้องการตั้งค่าประเภทอย่างรวดเร็ว ให้ใช้เหตุการณ์ pageswap และ pagereveal เพื่อจัดการค่าของ 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);
  }
});

ระบบจะไม่ส่งต่อประเภทดังกล่าวจากออบเจ็กต์ ViewTransition ในหน้าเก่าไปยังออบเจ็กต์ ViewTransition ของหน้าเว็บใหม่โดยอัตโนมัติ คุณต้องกำหนดประเภทที่จะใช้ในหน้าใหม่เป็นอย่างน้อยเพื่อให้ภาพเคลื่อนไหวทำงานตามที่คาดไว้

หากต้องการตอบสนองต่อประเภทเหล่านี้ ให้ใช้ตัวเลือกคลาส Pseudo ของ :active-view-transition-type() ในลักษณะเดียวกับการเปลี่ยนมุมมองเอกสารเดียวกัน

/* 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;
  }
}

เนื่องจากประเภทจะใช้กับการเปลี่ยนด้วยมุมมองแอ็กทีฟเท่านั้น ระบบจึงล้างประเภทโดยอัตโนมัติเมื่อการเปลี่ยนมุมมองเสร็จสมบูรณ์ ด้วยเหตุนี้ ประเภทจึงทำงานได้ดีกับฟีเจอร์อย่าง BFCache

ข้อมูลประชากร

ในการสาธิตการใส่เลขหน้าต่อไปนี้ เนื้อหาในหน้าจะเลื่อนไปข้างหน้าหรือข้างหลังตามหมายเลขหน้าที่คุณเข้าชม

การบันทึกการสาธิตการใส่เลขหน้า (MPA) ฟีเจอร์นี้ใช้การเปลี่ยนหน้าที่แตกต่างกัน โดยขึ้นอยู่กับหน้าที่คุณจะเข้าชม

ระบบจะพิจารณาประเภทการเปลี่ยนที่จะใช้ในเหตุการณ์ pagereveal และ pageswap ด้วยการดูที่ URL ไปยังและจาก 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';
  }
};

ความคิดเห็น

เรายินดีรับฟังความคิดเห็นจากนักพัฒนาแอปเสมอ หากต้องการแชร์ ให้แจ้งปัญหากับคณะทำงานของ CSS บน GitHub พร้อมคำแนะนำและคำถาม ใส่คำนำหน้าปัญหากับ [css-view-transitions] หากคุณพบข้อบกพร่อง ให้รายงานข้อบกพร่องของ Chromium แทน