ปัญหา
เว็บไซต์มักจะแสดงมุมมองหลายแบบของออบเจ็กต์ เช่น รายการผลิตภัณฑ์ และหน้ารายละเอียดของผลิตภัณฑ์แต่ละรายการ การสลับไปมาระหว่าง 2 มุมมองนี้มักทำให้รู้สึกไม่ต่อเนื่อง เมื่อผู้ใช้คลิกภาพขนาดย่อของผลิตภัณฑ์เพื่อดูรายละเอียด ภาพขนาดย่อจะหายไปและรูปภาพใหม่ที่มีขนาดใหญ่ขึ้นจะปรากฏขึ้นทันทีที่ตำแหน่งอื่นบนหน้าจอ การขาดความต่อเนื่องนี้ทำให้ผู้ใช้ติดตามความสัมพันธ์ระหว่างองค์ประกอบต่างๆ ได้ยากขึ้น
วิธีแก้ปัญหา
View Transitions API ช่วยให้คุณระบุคู่ขององค์ประกอบที่มีอยู่ในสถานะต่างๆ ก่อนและหลังการเปลี่ยนผ่านได้ เมื่อทริกเกอร์การเปลี่ยนด้วย document.startViewTransition() ในการนำทางหน้าเว็บเดียว (SPA) เบราว์เซอร์จะระบุองค์ประกอบที่ใช้ร่วมกันเหล่านี้ด้วย view-transition-name ที่ไม่ซ้ำกันที่ใช้ร่วมกัน จากนั้นระบบจะคำนวณความแตกต่างของตำแหน่ง ขนาด และการจัดสไตล์โดยอัตโนมัติ และทำให้องค์ประกอบเคลื่อนไหวอย่างราบรื่นจากสถานะเดิมไปยังสถานะใหม่ การเปลี่ยนนี้จะเกิดขึ้นในเลเยอร์บนสุด เหนือองค์ประกอบที่มีค่า z-index สูง
คู่มือการติดตั้งใช้งาน
ขั้นตอนที่ 1: รวมการเปลี่ยนแปลงสถานะไว้ใน startViewTransition
สำหรับแอปพลิเคชันหน้าเว็บเดียว (SPA) หรือการเปลี่ยนแปลงสถานะแบบง่าย ให้รวมตรรกะที่อัปเดต DOM ไว้ใน document.startViewTransition เบราว์เซอร์จะจับภาพสแนปชอตของสถานะปัจจุบัน เรียกใช้การอัปเดต แล้วจับภาพสถานะใหม่
function navigate(view) {
// MANDATORY: Wrap the update in startViewTransition
document.startViewTransition(() => updateDOM(view));
}
ขั้นตอนที่ 2: กำหนดชื่อการเปลี่ยนที่ใช้ร่วมกัน
ใช้พร็อพเพอร์ตี้ CSS view-transition-name เพื่อบอกเบราว์เซอร์ว่าควรเปลี่ยนรูปแบบองค์ประกอบใด ชื่อจะเป็นอะไรก็ได้ (ยกเว้น none) บังคับ: ต้องมีองค์ประกอบไม่เกิน 1 รายการก่อนและหลัง view-transition-name ที่ระบุ หากมีองค์ประกอบตั้งแต่ 2 รายการขึ้นไปที่มี view-transition-name ที่ระบุ ระบบจะอัปเดต DOM เป็นสถานะใหม่ทันทีโดยไม่มีการเปลี่ยนผ่าน
คุณใช้ view-transition-name หลายรายการเพื่อเปลี่ยนรูปร่างองค์ประกอบหลายคู่ได้ ตัวอย่างเช่น คุณอาจต้องการเปลี่ยนทั้งรูปภาพและชื่อผลิตภัณฑ์ด้วยการเปลี่ยนภาพที่แยกกัน
เนื่องจากมีหลายรายการในมุมมองรายการ คุณจึงไม่สามารถกำหนด view-transition-name เดียวกันให้กับทุกรายการได้ ปัญหานี้แก้ไขได้ 2 วิธีใน SPA
หน้ารายละเอียดแบบไดนามิก: กำหนด
view-transition-nameที่ไม่ซ้ำกันให้กับแต่ละรายการในหน้ารายการ แล้วใช้ชื่อนั้นกับองค์ประกอบที่ตรงกันในหน้ารายละเอียดแบบไดนามิกเมื่อเลือกรายการในลิสต์ ดังที่แสดงที่นี่/* In the list view, give each */ #product-1 { view-transition-name: p1 } #product-2 { view-transition-name: p2 } #product-3 { view-transition-name: p3 }function updateDOM(clickedTransitionName){ const hero = document.getElementById("hero"); hero.style.viewTransitionName = clickedTransitionName; }รายการแบบไดนามิก: กำหนด
view-transition-nameให้กับองค์ประกอบในหน้ารายละเอียด และใช้ชื่อนั้นกับรายการในหน้ารายการเมื่อเลือกองค์ประกอบดังกล่าว นำview-transition-nameออกจากรายการในหน้ารายการเมื่อกลับไปที่หน้ารายการ
องค์ประกอบ #hero ในหน้ารายละเอียดและองค์ประกอบ .thumbnail ที่เลือกในหน้ารายการใช้ view-transition-name ร่วมกัน
#hero{
view-transition-name: hero;
}
.thumbnail.selected {
view-transition-name: hero;
}
เมื่อมีการคลิกภาพขนาดย่อ เราต้องเตรียมมุมมองรายการโดยกำหนด view-transition-name โดยใช้ตัวเลือกคลาส .selected และทำการเปลี่ยนแปลง DOM ก่อนเริ่มการเปลี่ยนภาพ
จากนั้น คุณสามารถเรียกใช้ document.startViewTransition() และใช้การเปลี่ยนแปลงเพื่อเปลี่ยนหน้าจากมุมมองรายละเอียดเป็นมุมมองรายการ
หลังจากกลับไปที่มุมมองรายการแล้ว คุณต้องล้างคลาสการเปลี่ยนมุมมองเพื่อป้องกันไม่ให้การนำทางครั้งถัดไปเกิดข้อผิดพลาด คุณสามารถดำเนินการล้างข้อมูลนี้ได้หลังจากที่ Promise finished ของการเปลี่ยนผ่านได้รับการแก้ไขแล้ว
// Function called when a thumbnail is clicked
function goFromListToDetail(e){
e.currentTarget.classList.add("selected");
const hero = document.getElementById("hero");
const bgColor = getComputedStyle(e.currentTarget).backgroundColor;
hero.style.background = bgColor;
// Trigger the transition, checking for support
if (!document.startViewTransition) {
document.body.classList.add("detail");
// MANDATORY Accessibility Routing: Route focus to the newly revealed heading to announce context and preserve logical tab flow
document.getElementById("detail-heading")?.focus();
return; // MANDATORY: End function execution if view transitions are not supported.
}
const transition = document.startViewTransition(() => {
document.body.classList.add("detail");
});
// MANDATORY Accessibility Routing: Route focus after the view transition resolves
transition.finished.finally(() => {
document.getElementById("detail-heading")?.focus();
});
}
// Function called when navigating from detail back to list view
function goFromDetailToList() {
if (!document.startViewTransition) {
document.body.classList.remove("detail");
document.getElementById("list-heading")?.focus();
return;
}
const transition = document.startViewTransition(() => {
document.body.classList.remove("detail");
});
// Clean up the list view and route focus
transition.finished.finally(() => {
// Route focus back to list view
document.getElementById("list-heading")?.focus();
// Remove selected classList to remove view-transition-names
document.querySelectorAll(".selected").forEach(
(element) => {
element.classList.remove("selected");
},
);
});
}
โดยวิธีที่คุณเลือกจะขึ้นอยู่กับกรณีการใช้งาน รายการแบบไดนามิกต้องใช้ CSS ซ้ำน้อยลง แต่ต้องล้างข้อมูล JavaScript ด้วยตนเองมากขึ้น
ขั้นตอนที่ 3: แก้ไข "การยืด" สัดส่วนภาพ
โดยค่าเริ่มต้น เบราว์เซอร์จะเฟดภาพรวมเก่าและใหม่ภายในกลุ่มที่ยืดออกเพื่อให้พอดีกับทั้ง 2 ภาพ หากคุณกำลังเปลี่ยนข้อความ ให้ตั้งค่าความกว้างขององค์ประกอบของข้อความให้เป็น fit-content ทั้งในมุมมองเก่าและมุมมองใหม่ เพื่อให้สัดส่วนภาพขององค์ประกอบที่เปลี่ยนมีความเสถียร
#list-page .title {
width: fit-content;
}
#detail-page #title {
width: fit-content;
}
หากคุณกำลังเปลี่ยนองค์ประกอบที่เปลี่ยนสัดส่วนภาพ คุณอาจต้องตั้งค่าความสูงขององค์ประกอบเทียมเก่าและใหม่เป็น 100% ขององค์ประกอบเทียม ::view-transition-pair()
::view-transition-old(hero),
::view-transition-new(hero){
height: 100%;
}
องค์ประกอบจำลองเป็นภาพรวมขององค์ประกอบที่แสดงอยู่ คุณจึงใช้การประกาศ object-fit และ object-position เพื่อควบคุมเอฟเฟกต์การเปลี่ยนภาพได้มากขึ้น
แนวทางปฏิบัติแนะนำ
- อย่าระบุการเปลี่ยนผ่านมากเกินไป ใช้องค์ประกอบที่แชร์สำหรับเนื้อหาหลักที่ผู้ใช้กำลังติดตามอยู่เท่านั้น (เช่น รูปภาพหลัก ส่วนหัว)
- ควรนำค่า
view-transition-nameชั่วคราวออกหลังจากการเปลี่ยนผ่านเสร็จสิ้นเพื่อหลีกเลี่ยงผลข้างเคียงในการเปลี่ยนผ่านในอนาคต - อย่าเปลี่ยนองค์ประกอบที่มีภาพเคลื่อนไหวที่ทำงานอยู่ การเปลี่ยนมุมมองจะทำงานกับภาพรวม ดังนั้นภาพเคลื่อนไหวทั้งหมดจะหยุดชั่วคราวระหว่างการเปลี่ยนมุมมอง
- ควรเคารพค่ากำหนดของผู้ใช้สำหรับการลดการเคลื่อนไหวโดยใช้การค้นหาสื่อ
prefers-reduced-motion - การกำหนดเส้นทางการช่วยเหลือพิเศษที่จำเป็น: การเปลี่ยนมุมมองจะเปลี่ยนรูปแบบเลย์เอาต์ของหน้าเว็บแบบไดนามิก แต่จะไม่จัดการโฟกัสแบบเป็นโปรแกรม หากโฟกัสยังคงอยู่ที่องค์ประกอบที่ซ่อนอยู่หรือถูกนำออกระหว่างการเปลี่ยนผ่าน ระบบจะละทิ้งโฟกัส ทำให้ผู้ใช้แป้นพิมพ์และเทคโนโลยีความช่วยเหลือพิเศษไม่มีบริบท เปลี่ยนโฟกัสแบบเป็นโปรแกรมไปยังส่วนหัวของหน้าหรือคอนเทนเนอร์มุมมองที่อัปเดต (โดยใช้
tabindex="-1") ทันทีหลังจากที่ DOM อัปเดตหรือเมื่อสัญญาfinishedของการเปลี่ยนมุมมองได้รับการแก้ไข
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}
กลยุทธ์สำรอง
View Transitions API ออกแบบมาเพื่อการเพิ่มประสิทธิภาพแบบต่อเนื่อง เบราว์เซอร์ที่ไม่รองรับจะดำเนินการอัปเดต DOM ทันทีโดยไม่มีภาพเคลื่อนไหว
function navigate(){
if (!document.startViewTransition) {
// Fallback: Just update the DOM
updateDOM();
} else {
document.startViewTransition(() => updateDOM());
}
}