เมื่อมีการเปลี่ยนมุมมองระหว่างเอกสาร 2 รายการ จะเรียกว่าการเปลี่ยนมุมมองข้ามเอกสาร ซึ่งมักจะพบในแอปพลิเคชันที่มีหลายหน้า (MPA) Chrome จาก Chrome 126 รองรับการเปลี่ยนมุมมองข้ามเอกสาร
การรองรับเบราว์เซอร์
การเปลี่ยนมุมมองข้ามเอกสารอาศัยองค์ประกอบพื้นฐานและหลักการเดียวกันกับการเปลี่ยนมุมมองเอกสารเดียวกัน ซึ่งคุณตั้งใจสร้างไว้ดังนี้
- เบราว์เซอร์จะบันทึกสแนปชอตขององค์ประกอบที่มี
view-transition-name
ที่ไม่ซ้ำกันทั้งในหน้าเก่าและใหม่ - DOM จะได้รับการอัปเดตขณะที่การแสดงผลถูกระงับ
- และสุดท้าย การเปลี่ยนขับเคลื่อนโดยภาพเคลื่อนไหว CSS
ความแตกต่างเมื่อเทียบกับการเปลี่ยนมุมมองเอกสารเดียวกันคือเมื่อใช้การเปลี่ยนมุมมองข้ามเอกสาร คุณไม่จําเป็นต้องเรียกใช้ document.startViewTransition
เพื่อเริ่มการเปลี่ยนมุมมอง แต่ทริกเกอร์การเปลี่ยนมุมมองข้ามเอกสารจะเป็นการนำทางจากต้นทางเดียวกันจากหน้าหนึ่งไปยังอีกหน้าหนึ่ง ซึ่งเป็นการทำงานที่ผู้ใช้ของเว็บไซต์มักจะดำเนินการเมื่อคลิกลิงก์
กล่าวคือ ไม่มี API ให้เรียกใช้เพื่อเริ่มการเปลี่ยนมุมมองระหว่างเอกสาร 2 รายการ อย่างไรก็ตาม มี 2 เงื่อนไขที่จะต้องปฏิบัติตาม ได้แก่
- เอกสารทั้ง 2 รายการต้องมาจากต้นทางเดียวกัน
- คุณต้องเลือกใช้ทั้ง 2 หน้าเพื่ออนุญาตให้เปลี่ยนมุมมอง
ทั้ง 2 เงื่อนไขนี้จะอธิบายภายหลังในเอกสารนี้
การเปลี่ยนมุมมองข้ามเอกสารจะจำกัดให้แสดงเฉพาะการนำทางจากต้นทางเดียวกัน
การเปลี่ยนมุมมองข้ามเอกสารจะจำกัดอยู่ที่การนำทางจากต้นทางเดียวกันเท่านั้น การนำทางจะถือว่ามีแหล่งที่มาเดียวกันหากที่มาของหน้าที่เข้าร่วมทั้ง 2 หน้าเหมือนกัน
ต้นทางของหน้าเว็บเป็นการรวมกันของรูปแบบที่ใช้ ชื่อโฮสต์ และพอร์ต ตามที่ดูรายละเอียดใน web.dev
เช่น คุณสามารถมีการเปลี่ยนมุมมองข้ามเอกสารเมื่อไปยังส่วนต่างๆ จาก developer.chrome.com
ไปยัง developer.chrome.com/blog
เนื่องจากทรัพยากรเหล่านั้นมาจากต้นทางเดียวกัน
คุณไม่สามารถเปลี่ยนผ่านดังกล่าวเมื่อนำทางจาก developer.chrome.com
ไป www.chrome.com
ได้เนื่องจากเป็นแบบข้ามต้นทางและเว็บไซต์เดียวกัน
เลือกใช้การเปลี่ยนมุมมองข้ามเอกสาร
หากต้องการให้มีการเปลี่ยนมุมมองข้ามเอกสารระหว่างเอกสาร 2 ฉบับ หน้าที่เข้าร่วมทั้ง 2 หน้าจะต้องเลือกใช้เพื่ออนุญาตการดำเนินการนี้ ซึ่งทำได้ด้วยกฎ at @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()
การเปลี่ยนมุมมองจะทริกเกอร์จากหน้าหนึ่งไปอีกหน้าหนึ่ง
ปรับแต่งการเปลี่ยนมุมมองข้ามเอกสาร
หากต้องการปรับแต่งการเปลี่ยนมุมมองข้ามเอกสาร คุณสามารถใช้ฟีเจอร์บางอย่างของแพลตฟอร์มเว็บได้
ฟีเจอร์เหล่านี้ไม่ได้เป็นส่วนหนึ่งของข้อกำหนดของ View Transition API เอง แต่ออกแบบมาเพื่อใช้ร่วมกับคุณสมบัติดังกล่าว
การแข่งขัน pageswap
และ pagereveal
ข้อกำหนดของ 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
ได้
ข้อมูลการเปิดใช้งานการนำทาง
คุณยังดำเนินการกับทั้งเหตุการณ์ 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 เฉพาะบนองค์ประกอบที่จำเป็นต้องใช้
โค้ดมีดังต่อไปนี้
// 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);
}
}
});
รอให้เนื้อหาโหลดโดยใช้การบล็อกการแสดงผล
การรองรับเบราว์เซอร์
ในบางกรณี คุณอาจต้องระงับการแสดงผลครั้งแรกของหน้าเว็บไว้จนกว่าองค์ประกอบบางอย่างจะปรากฏใน 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
ของหน้าใหม่โดยอัตโนมัติ คุณต้องระบุประเภทที่จะใช้ในหน้าใหม่เป็นอย่างน้อยเพื่อให้ภาพเคลื่อนไหวทำงานตามที่คาดไว้
หากต้องการตอบสนองต่อประเภทเหล่านี้ ให้ใช้ตัวเลือกคลาสเทียม :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
สาธิต
ในการสาธิตการใส่เลขหน้าต่อไปนี้ เนื้อหาของหน้าจะเลื่อนไปข้างหน้าหรือข้างหลังตามหมายเลขหน้าที่คุณกำลังนำทางไป
ประเภทการเปลี่ยนที่จะใช้ระบุในเหตุการณ์ pagereveal
และ pageswap
โดยดูจาก 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 แทน