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

เมื่อการเปลี่ยนมุมมองทำงานในเอกสารเดียว จะเรียกว่าการเปลี่ยนมุมมองเอกสารเดียวกัน กรณีนี้มักเกิดขึ้นในแอปพลิเคชันหน้าเว็บเดียว (SPA) ที่ใช้ JavaScript เพื่ออัปเดต DOM รองรับการเปลี่ยนมุมมองเอกสารเดียวกันใน Chrome ตั้งแต่ Chrome 111 เป็นต้นไป

หากต้องการทริกเกอร์การเปลี่ยนมุมมองเอกสารเดียวกัน ให้เรียก document.startViewTransition:

function handleClick(e) {
  // Fallback for browsers that don't support this API:
  if (!document.startViewTransition) {
    updateTheDOMSomehow();
    return;
  }

  // With a View Transition:
  document.startViewTransition(() => updateTheDOMSomehow());
}

เมื่อเรียกใช้ เบราว์เซอร์จะบันทึกสแนปชอตขององค์ประกอบทั้งหมดที่มีการประกาศคุณสมบัติ CSS view-transition-name โดยอัตโนมัติ

จากนั้นจะเรียกใช้ที่ส่งผ่านในโค้ดเรียกกลับที่อัปเดต DOM จากนั้นจะประมวลผลสแนปชอตของสถานะใหม่

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


การเปลี่ยนเริ่มต้น: เฟดแบบครอสเฟด

การเปลี่ยนมุมมองเริ่มต้นจะเป็นแบบจางลง ดังนั้นจึงเป็นการเริ่มต้น API ที่ดี:

function spaNavigate(data) {
  // Fallback for browsers that don't support this API:
  if (!document.startViewTransition) {
    updateTheDOMSomehow(data);
    return;
  }

  // With a transition:
  document.startViewTransition(() => updateTheDOMSomehow(data));
}

ตำแหน่งที่ updateTheDOMSomehow เปลี่ยน DOM เป็นสถานะใหม่ ซึ่งสามารถทำอย่างไรก็ได้ตามที่คุณต้องการ เช่น เพิ่มหรือนำองค์ประกอบออก เปลี่ยนชื่อคลาส หรือเปลี่ยนรูปแบบ

ในลักษณะเช่นนี้ หน้าต่างๆ จะค่อยๆ เลือนหายไป:

ครอสเฟดเริ่มต้น การสาธิตเพียงเล็กน้อย แหล่งที่มา

โอเค การครอสเฟดไม่น่าประทับใจขนาดนั้น โชคดีที่สามารถปรับแต่งทรานซิชันได้ แต่ก่อนอื่นคุณต้องเข้าใจวิธีการทำงานของการเฟดแบบพื้นฐานนี้


วิธีการทำงานของการเปลี่ยนเหล่านี้

มาอัปเดตตัวอย่างโค้ดก่อนหน้ากัน

document.startViewTransition(() => updateTheDOMSomehow(data));

เมื่อมีการเรียก .startViewTransition() API จะบันทึกสถานะปัจจุบันของหน้า ซึ่งรวมถึงการเก็บภาพสแนปชอตด้วย

เมื่อดำเนินการเสร็จสิ้น ระบบจะโทรเรียกกลับที่ส่งไปยัง .startViewTransition() ซึ่งเป็นที่ที่ DOM เปลี่ยนไป จากนั้น API จะบันทึกสถานะใหม่ของหน้าเว็บ

เมื่อบันทึกสถานะใหม่แล้ว API จะสร้างโครงสร้างองค์ประกอบจำลองดังนี้

::view-transition
└─ ::view-transition-group(root)
   └─ ::view-transition-image-pair(root)
      ├─ ::view-transition-old(root)
      └─ ::view-transition-new(root)

::view-transition จะอยู่ซ้อนทับกับส่วนอื่นๆ ในหน้าเว็บ ซึ่งจะเป็นประโยชน์หากคุณต้องการตั้งค่าสีพื้นหลังสำหรับการเปลี่ยน

::view-transition-old(root) เป็นภาพหน้าจอของมุมมองเก่า และ ::view-transition-new(root) คือมุมมองใหม่แบบสด ทั้ง 2 รายการจะแสดงผลเป็น "เนื้อหาที่แทนที่" ของ CSS (เช่น <img>)

มุมมองเดิมจะเคลื่อนไหวจาก opacity: 1 ไปยัง opacity: 0 ในขณะที่มุมมองใหม่จะเคลื่อนไหวจาก opacity: 0 ไปยัง opacity: 1 ซึ่งทำให้เกิดภาพเฟด

ภาพเคลื่อนไหวทั้งหมดดำเนินการโดยใช้ภาพเคลื่อนไหว CSS ดังนั้นจึงสามารถปรับแต่งด้วย CSS ได้

ปรับแต่งการเปลี่ยน

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

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 5s;
}

การเปลี่ยนแปลงเพียงครั้งเดียวทำให้การค่อยๆ จางลงช้าลงมาก:

ครอสเฟดแบบยาว การสาธิตเพียงเล็กน้อย แหล่งที่มา

โอเค ยังไม่น่าประทับใจ แต่โค้ดต่อไปนี้จะใช้การเปลี่ยนแกนที่ใช้ร่วมกันของ Material Design แทน

@keyframes fade-in {
  from { opacity: 0; }
}

@keyframes fade-out {
  to { opacity: 0; }
}

@keyframes slide-from-right {
  from { transform: translateX(30px); }
}

@keyframes slide-to-left {
  to { transform: translateX(-30px); }
}

::view-transition-old(root) {
  animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
    300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
}

::view-transition-new(root) {
  animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
    300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}

และผลที่ได้มีดังนี้

การเปลี่ยนแกนที่ใช้ร่วมกัน การสาธิตเพียงเล็กน้อย แหล่งที่มา

การเปลี่ยนองค์ประกอบหลายรายการ

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

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

.main-header {
  view-transition-name: main-header;
}

ค่าของ view-transition-name อาจเป็นค่าใดก็ได้ที่คุณต้องการ (ยกเว้น none ซึ่งหมายความว่าจะไม่มีชื่อการเปลี่ยน) ซึ่งใช้เพื่อระบุองค์ประกอบในการเปลี่ยนแปลงอย่างไม่ซ้ำกัน

ผลลัพธ์ที่ได้คือ

การเปลี่ยนแกนที่ใช้ร่วมกันที่มีส่วนหัวแบบคงที่ การสาธิตเพียงเล็กน้อย แหล่งที่มา

ตอนนี้ส่วนหัวจะอยู่ที่เดิมและจางลง

การประกาศ CSS นั้นทำให้แผนผังองค์ประกอบจำลองมีการเปลี่ยนแปลงดังนี้

::view-transition
├─ ::view-transition-group(root)
│  └─ ::view-transition-image-pair(root)
│     ├─ ::view-transition-old(root)
│     └─ ::view-transition-new(root)
└─ ::view-transition-group(main-header)
   └─ ::view-transition-image-pair(main-header)
      ├─ ::view-transition-old(main-header)
      └─ ::view-transition-new(main-header)

ขณะนี้มีกลุ่มการเปลี่ยน 2 กลุ่ม อันหนึ่งสำหรับส่วนหัวและอีกจุดหนึ่งสำหรับที่เหลือ ซึ่งสามารถกำหนดเป้าหมายได้อย่างอิสระด้วย CSS และมีการเปลี่ยนแปลงที่แตกต่างกัน แต่ในกรณีนี้ main-header จะพบกับการเปลี่ยนที่เป็นค่าเริ่มต้น ซึ่งก็คือการเฟดแบบต่อเนื่อง

เอาล่ะ การเปลี่ยนเริ่มต้นไม่ได้เป็นแบบครอสเฟดเท่านั้น แต่ ::view-transition-group ยังเปลี่ยนด้วย

  • ตำแหน่งและการเปลี่ยนรูปแบบ (โดยใช้ transform)
  • ความกว้าง
  • ส่วนสูง

ซึ่งยังไม่มีเรื่องสำคัญในขณะนี้เนื่องจากส่วนหัวมีขนาดเท่ากันและตำแหน่งทั้ง 2 ฝั่งของการเปลี่ยนแปลง DOM แต่คุณยังแยกข้อความในส่วนหัวได้โดยทำดังนี้

.main-header-text {
  view-transition-name: main-header-text;
  width: fit-content;
}

มีการใช้ fit-content โดยให้องค์ประกอบมีขนาดเท่ากับข้อความ แทนที่จะยืดขยายจนเต็มความกว้างที่เหลืออยู่ หากไม่มีค่านี้ ลูกศรย้อนกลับจะลดขนาดขององค์ประกอบข้อความส่วนหัวแทนที่จะลดขนาดเท่ากันในทั้ง 2 หน้า

ตอนนี้เรามี 3 ส่วนที่จะลองเล่น

::view-transition
├─ ::view-transition-group(root)
│  └─ …
├─ ::view-transition-group(main-header)
│  └─ …
└─ ::view-transition-group(main-header-text)
   └─ …

แต่จะใช้ค่าเริ่มต้นดังนี้

ข้อความส่วนหัวแบบเลื่อน การสาธิตเพียงเล็กน้อย แหล่งที่มา

ตอนนี้ข้อความหัวข้อดูน่าพอใจเล็กน้อยจนสามารถเลื่อนผ่านเพื่อเพิ่มพื้นที่สำหรับปุ่มย้อนกลับ


ทำให้องค์ประกอบจำลองหลายตัวเคลื่อนไหวด้วยวิธีเดียวกันด้วย view-transition-class

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

  • 125
  • 125
  • x
  • x

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

h1 {
    view-transition-name: title;
}
::view-transition-group(title) {
    animation-timing-function: ease-in-out;
}

#card1 { view-transition-name: card1; }
#card2 { view-transition-name: card2; }
#card3 { view-transition-name: card3; }
#card4 { view-transition-name: card4; }
…
#card20 { view-transition-name: card20; }

::view-transition-group(card1),
::view-transition-group(card2),
::view-transition-group(card3),
::view-transition-group(card4),
…
::view-transition-group(card20) {
    animation-timing-function: var(--bounce);
}

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

คุณสามารถใช้ view-transition-class ในองค์ประกอบจำลองการเปลี่ยนมุมมองเพื่อใช้กฎรูปแบบเดียวกันได้

#card1 { view-transition-name: card1; }
#card2 { view-transition-name: card2; }
#card3 { view-transition-name: card3; }
#card4 { view-transition-name: card4; }
#card5 { view-transition-name: card5; }
…
#card20 { view-transition-name: card20; }

#cards-wrapper > div {
  view-transition-class: card;
}
html::view-transition-group(.card) {
  animation-timing-function: var(--bounce);
}

ตัวอย่างการ์ดต่อไปนี้ใช้ประโยชน์จากข้อมูลโค้ด CSS ก่อนหน้านี้ การ์ดทั้งหมดรวมถึงการ์ดที่เพิ่มใหม่จะใช้เวลาเดียวกันโดยมีตัวเลือกเดียวคือ html::view-transition-group(.card)

การบันทึกการสาธิตการ์ด การใช้ view-transition-class จะมีผลกับ animation-timing-function เดียวกันกับการ์ดทั้งหมด ยกเว้นการ์ดที่เพิ่มหรือนำออก

การแก้ไขข้อบกพร่องของการเปลี่ยน

เนื่องจากการเปลี่ยนมุมมองสร้างขึ้นจากภาพเคลื่อนไหว CSS แผงภาพเคลื่อนไหวในเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome จึงเหมาะสําหรับการแก้ไขข้อบกพร่องการเปลี่ยน

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

การแก้ไขข้อบกพร่องของการเปลี่ยนมุมมองด้วยเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome

องค์ประกอบที่เปลี่ยนไม่จำเป็นต้องเป็นองค์ประกอบ DOM เดียวกัน

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

เช่น การฝังวิดีโอหลักอาจได้รับ view-transition-name ดังนี้

.full-embed {
  view-transition-name: full-embed;
}

จากนั้น เมื่อมีการคลิกภาพขนาดย่อ ก็จะใช้ view-transition-name เหมือนกันได้ในช่วงการเปลี่ยนผ่านเท่านั้น

thumbnail.onclick = async () => {
  thumbnail.style.viewTransitionName = 'full-embed';

  document.startViewTransition(() => {
    thumbnail.style.viewTransitionName = '';
    updateTheDOMSomehow();
  });
};

แล้วผลลัพธ์ที่ได้ก็คือ

องค์ประกอบหนึ่งกำลังเปลี่ยนไปเป็นอีกองค์ประกอบหนึ่ง การสาธิตเพียงเล็กน้อย แหล่งที่มา

ตอนนี้ภาพขนาดย่อจะเปลี่ยนเป็นรูปภาพหลัก แม้ว่าองค์ประกอบทั้งสองจะมีแนวคิด (และจริงๆ ก็ตาม) ต่างกัน แต่ API การเปลี่ยนถือว่าทั้งคู่เป็นสิ่งเดียวกัน เพราะใช้ view-transition-name เดียวกัน

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


การเปลี่ยนเข้าและออกที่กำหนดเอง

ดูตัวอย่างต่อไปนี้

การเข้าสู่และออกจากแถบด้านข้าง การสาธิตเพียงเล็กน้อย แหล่งที่มา

ซึ่งแถบด้านข้างเป็นส่วนหนึ่งของการเปลี่ยนแปลงดังกล่าว โดยมีรายละเอียดดังนี้

.sidebar {
  view-transition-name: sidebar;
}

แต่ต่างจากส่วนหัวในตัวอย่างก่อนหน้านี้ตรงที่แถบด้านข้างไม่ปรากฏในทุกหน้า หากทั้ง 2 สถานะมีแถบด้านข้าง องค์ประกอบจำลองของการเปลี่ยนจะมีลักษณะดังนี้

::view-transition
├─ …other transition groups…
└─ ::view-transition-group(sidebar)
   └─ ::view-transition-image-pair(sidebar)
      ├─ ::view-transition-old(sidebar)
      └─ ::view-transition-new(sidebar)

แต่หากแถบด้านข้างอยู่ในหน้าใหม่เท่านั้น องค์ประกอบจำลอง ::view-transition-old(sidebar) จะไม่อยู่ที่นั่น เนื่องจากไม่มีรูปภาพ "เก่า" สำหรับแถบด้านข้าง คู่รูปภาพจึงมี ::view-transition-new(sidebar) เท่านั้น ในทำนองเดียวกัน หากแถบด้านข้างอยู่ในหน้าเก่าเท่านั้น คู่รูปภาพจะมีเพียง ::view-transition-old(sidebar) เท่านั้น

ในเดโมก่อนหน้านี้ แถบด้านข้างจะเปลี่ยนไปตามการเข้าสู่ ออก หรือแสดงอยู่ในทั้ง 2 สถานะ ตัวหนังสือจะเข้ามาโดยเลื่อนจากด้านขวาแล้วค่อยๆ เข้ามา ออกโดยเลื่อนไปทางขวาและค่อยๆ จางออก และติดอยู่ในที่เดิมเมื่ออยู่ในทั้ง 2 สถานะ

หากต้องการสร้างการเปลี่ยนเข้าและออกที่เฉพาะเจาะจง คุณสามารถใช้คลาสเทียม :only-child เพื่อกำหนดเป้าหมายองค์ประกอบเทียมเก่าหรือใหม่เมื่อเป็นองค์ประกอบย่อยเพียงรายการเดียวในคู่รูปภาพ ดังนี้

/* Entry transition */
::view-transition-new(sidebar):only-child {
  animation: 300ms cubic-bezier(0, 0, 0.2, 1) both fade-in,
    300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}

/* Exit transition */
::view-transition-old(sidebar):only-child {
  animation: 150ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
    300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-right;
}

ในกรณีนี้จะไม่มีการเปลี่ยนแปลงเฉพาะเมื่อแถบด้านข้างปรากฏในทั้ง 2 สถานะ เนื่องจากค่าเริ่มต้นจะเหมาะสมที่สุด

การอัปเดต DOM ที่ไม่พร้อมกันและกำลังรอเนื้อหา

โค้ดเรียกกลับที่ส่งไปยัง .startViewTransition() สามารถส่งกลับสัญญาได้ ซึ่งช่วยให้อัปเดต DOM ไม่พร้อมกันและรอให้เนื้อหาสำคัญพร้อมใช้งาน

document.startViewTransition(async () => {
  await something;
  await updateTheDOMSomehow();
  await somethingElse;
});

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

หากคุณตัดสินใจรอให้รูปภาพหรือแบบอักษรพร้อมใช้งาน ให้ใช้การหมดเวลาที่เข้มงวดดังนี้

const wait = ms => new Promise(r => setTimeout(r, ms));

document.startViewTransition(async () => {
  updateTheDOMSomehow();

  // Pause for up to 100ms for fonts to be ready:
  await Promise.race([document.fonts.ready, wait(100)]);
});

อย่างไรก็ตาม ในบางกรณีคุณควรหลีกเลี่ยงความล่าช้าทั้งหมดและใช้เนื้อหาที่มีอยู่แล้ว


ใช้ประโยชน์สูงสุดจากเนื้อหาที่มีอยู่

ในกรณีที่ภาพปกเปลี่ยนไปเป็นรูปภาพขนาดใหญ่

การเปลี่ยนภาพปกเป็นรูปภาพที่ใหญ่ขึ้น ลองใช้เว็บไซต์เดโม

การเปลี่ยนเริ่มต้นคือแบบข้ามซีก ซึ่งหมายความว่าภาพขนาดย่ออาจจางลงด้วยรูปภาพขนาดเต็มที่ยังไม่ได้โหลด

วิธีหนึ่งในการจัดการปัญหานี้คือการรอให้รูปภาพขนาดเต็มโหลดเสร็จก่อน แล้วจึงค่อยเริ่มเปลี่ยน ตามหลักการแล้ว ควรดำเนินการก่อนเรียกใช้ .startViewTransition() เพื่อให้หน้าเว็บยังคงโต้ตอบได้ และสามารถแสดงตัวหมุนเพื่อบอกผู้ใช้ว่ากำลังโหลดสิ่งต่างๆ อยู่ แต่ในกรณีนี้ยังมีวิธีที่ดีกว่า:

::view-transition-old(full-embed),
::view-transition-new(full-embed) {
  /* Prevent the default animation,
  so both views remain opacity:1 throughout the transition */
  animation: none;
  /* Use normal blending,
  so the new view sits on top and obscures the old view */
  mix-blend-mode: normal;
}

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

วิธีนี้จะไม่ทำงานหากมุมมองใหม่แสดงความโปร่งใส แต่ในกรณีนี้เราทราบว่าไม่เป็นเช่นนั้น เราจึงสามารถดำเนินการเพิ่มประสิทธิภาพนี้ได้

รับมือกับการเปลี่ยนแปลงของสัดส่วนภาพ

ตามความสะดวกแล้ว ทรานซิชันทั้งหมดก่อนหน้านี้เป็นองค์ประกอบที่มีอัตราส่วนเท่ากัน แต่อาจไม่เป็นเช่นนั้นเสมอไป จะเกิดอะไรขึ้นหากภาพขนาดย่อคือ 1:1 และรูปภาพหลักคือ 16:9

องค์ประกอบหนึ่งเปลี่ยนไปเป็นอีกองค์ประกอบหนึ่งโดยมีการเปลี่ยนแปลงสัดส่วนภาพ การสาธิตเพียงเล็กน้อย แหล่งที่มา

ในการเปลี่ยนเริ่มต้น กลุ่มจะเคลื่อนไหวจากขนาดก่อนเป็นขนาดหลัง มุมมองเก่าและใหม่จะมีความกว้าง 100% ของกลุ่มและความสูงอัตโนมัติซึ่งหมายความว่ามุมมองเหล่านี้จะรักษาอัตราส่วนไว้โดยไม่คำนึงถึงขนาดของกลุ่ม

นี่คือค่าเริ่มต้นที่ดี แต่นี่ไม่ใช่สิ่งที่คุณต้องการในกรณีนี้ ดังนั้น

::view-transition-old(full-embed),
::view-transition-new(full-embed) {
  /* Prevent the default animation,
  so both views remain opacity:1 throughout the transition */
  animation: none;
  /* Use normal blending,
  so the new view sits on top and obscures the old view */
  mix-blend-mode: normal;
  /* Make the height the same as the group,
  meaning the view size might not match its aspect-ratio. */
  height: 100%;
  /* Clip any overflow of the view */
  overflow: clip;
}

/* The old view is the thumbnail */
::view-transition-old(full-embed) {
  /* Maintain the aspect ratio of the view,
  by shrinking it to fit within the bounds of the element */
  object-fit: contain;
}

/* The new view is the full image */
::view-transition-new(full-embed) {
  /* Maintain the aspect ratio of the view,
  by growing it to cover the bounds of the element */
  object-fit: cover;
}

ซึ่งหมายความว่าภาพขนาดย่อจะอยู่ตรงกลางขององค์ประกอบเมื่อความกว้างขยายออก แต่รูปภาพแบบเต็มจะ "ยกเลิกการครอบตัด" เนื่องจากจะเปลี่ยนจาก 1:1 เป็น 16:9

ดูข้อมูลโดยละเอียดเพิ่มเติมได้ที่ (ดูการเปลี่ยน: การจัดการการเปลี่ยนแปลงสัดส่วนภาพ)(https://jakearchibald.com/2024/view-transitions-handling-aspect-ratio-changes/)


ใช้คำสืบค้นสื่อเพื่อเปลี่ยนการเปลี่ยนสำหรับสถานะต่างๆ ของอุปกรณ์

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

องค์ประกอบหนึ่งกำลังเปลี่ยนไปเป็นอีกองค์ประกอบหนึ่ง การสาธิตเพียงเล็กน้อย แหล่งที่มา

ซึ่งทำได้ด้วยการใช้คำค้นหาสื่อทั่วไป ดังนี้

/* Transitions for mobile */
::view-transition-old(root) {
  animation: 300ms ease-out both full-slide-to-left;
}

::view-transition-new(root) {
  animation: 300ms ease-out both full-slide-from-right;
}

@media (min-width: 500px) {
  /* Overrides for larger displays.
  This is the shared axis transition from earlier in the article. */
  ::view-transition-old(root) {
    animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
      300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
  }

  ::view-transition-new(root) {
    animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
      300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
  }
}

คุณยังอาจต้องเปลี่ยนองค์ประกอบที่คุณกำหนด view-transition-name โดยขึ้นอยู่กับคำค้นหาสื่อที่ตรงกัน


ตอบสนองต่อความต้องการ "การเคลื่อนไหวที่ลดลง"

ผู้ใช้ระบุได้ว่าต้องการลดการเคลื่อนไหวผ่านระบบปฏิบัติการ และค่ากำหนดดังกล่าวจะแสดงใน CSS

คุณสามารถเลือกที่จะป้องกันไม่ให้มีการเปลี่ยนแปลงใดๆ สำหรับผู้ใช้เหล่านี้

@media (prefers-reduced-motion) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

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


จัดการรูปแบบการเปลี่ยนมุมมองที่หลากหลายด้วยประเภทการเปลี่ยนมุมมอง

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

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

สำหรับวิธีนี้ คุณสามารถใช้ประเภทการเปลี่ยนแบบมุมมอง ซึ่งให้คุณกำหนดประเภทให้กับการเปลี่ยนด้วยมุมมองแอ็กทีฟได้อย่างน้อย 1 ประเภท เช่น เมื่อเปลี่ยนเป็นหน้าที่สูงกว่าในลำดับการใส่เลขหน้า ให้ใช้ประเภท forwards และเมื่อไปที่หน้าล่าง ให้ใช้ประเภท backwards ประเภทเหล่านี้จะใช้ได้เมื่อมีการบันทึกหรือทำการเปลี่ยนเท่านั้น และแต่ละประเภทสามารถปรับแต่งผ่าน CSS เพื่อใช้ภาพเคลื่อนไหวที่แตกต่างกันได้

หากต้องการใช้ประเภทในการเปลี่ยนมุมมองเอกสารเดียวกัน ให้ส่ง types ไปยังเมธอด startViewTransition เพื่ออนุญาตการดำเนินการนี้ document.startViewTransition จะยอมรับออบเจ็กต์ด้วย โดย update เป็นฟังก์ชันเรียกกลับที่อัปเดต DOM และ types คืออาร์เรย์ที่มีประเภท

const direction = determineBackwardsOrForwards();

const t = document.startViewTransition({
  update: updateTheDOMSomehow,
  types: ['slide', direction],
});

หากต้องการตอบกลับประเภทเหล่านี้ ให้ใช้ตัวเลือก :active-view-transition-type() ส่งtypeที่ต้องการกำหนดเป้าหมายไปยังตัวเลือก วิธีนี้ช่วยให้คุณแยกสไตล์ของการเปลี่ยนมุมมองหลายรายการออกจากกัน โดยไม่ต้องการประกาศของมุมมองหนึ่งรบกวนการประกาศของอีกมุมมองหนึ่ง

เนื่องจากประเภทจะใช้เมื่อบันทึกหรือทําการเปลี่ยนเท่านั้น คุณจึงใช้ตัวเลือกเพื่อตั้งค่าหรือไม่ได้ตั้งค่า view-transition-name ในองค์ประกอบสําหรับการเปลี่ยนมุมมองด้วยประเภทดังกล่าวได้เท่านั้น

/* 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 (using the default root snapshot) */
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;
  }
}

ในการสาธิตการใส่เลขหน้าต่อไปนี้ เนื้อหาในหน้าจะเลื่อนไปข้างหน้าหรือข้างหลังตามหมายเลขหน้าที่คุณเข้าชม ประเภทจะได้รับการกำหนดเมื่อคลิกที่ระบบจะส่งผ่านไปยัง document.startViewTransition

หากต้องการกำหนดเป้าหมายการเปลี่ยนมุมมองแอ็กทีฟใดก็ได้ ไม่ว่าจะเป็นประเภทใดก็ตาม ให้ใช้ตัวเลือกคลาส Pseudo ของ :active-view-transition แทน

html:active-view-transition {
    …
}

จัดการรูปแบบการเปลี่ยนมุมมองหลายรายการด้วยชื่อคลาสในรูทการเปลี่ยนมุมมอง

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

การเปลี่ยนแบบต่างๆ เมื่อ "ย้อนกลับ" การสาธิตเพียงเล็กน้อย แหล่งที่มา

ก่อนประเภทการเปลี่ยน วิธีจัดการกรณีเหล่านี้คือการตั้งชื่อชั้นเรียนในรูทการเปลี่ยนชั่วคราว เมื่อเรียกใช้ document.startViewTransition รูทการเปลี่ยนนี้คือองค์ประกอบ <html> ซึ่งเข้าถึงได้โดยใช้ document.documentElement ใน JavaScript:

if (isBackNavigation) {
  document.documentElement.classList.add('back-transition');
}

const transition = document.startViewTransition(() =>
  updateTheDOMSomehow(data)
);

try {
  await transition.finished;
} finally {
  document.documentElement.classList.remove('back-transition');
}

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

ตอนนี้คุณสามารถใช้ชื่อคลาสดังกล่าวใน CSS เพื่อเปลี่ยนการเปลี่ยนได้ ดังนี้

/* 'Forward' transitions */
::view-transition-old(root) {
  animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
    300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
}

::view-transition-new(root) {
  animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in, 300ms
      cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}

/* Overrides for 'back' transitions */
.back-transition::view-transition-old(root) {
  animation-name: fade-out, slide-to-right;
}

.back-transition::view-transition-new(root) {
  animation-name: fade-in, slide-from-left;
}

เช่นเดียวกับคำค้นหาสื่อ การมีอยู่ของคลาสเหล่านี้อาจใช้เพื่อเปลี่ยนองค์ประกอบที่จะได้รับ view-transition-name ได้เช่นกัน


เรียกใช้การเปลี่ยนฉากโดยไม่ตรึงภาพเคลื่อนไหวอื่นๆ

ดูการสาธิตตำแหน่งการเปลี่ยนวิดีโอ

การเปลี่ยนวิดีโอ การสาธิตเพียงเล็กน้อย แหล่งที่มา

คุณเห็นสิ่งผิดปกติหรือไม่ ไม่ต้องกังวลหากคุณไม่ได้ใช้งาน ตรงนี้ช้าลงเลย:

วิดีโอเปลี่ยน ช้าลง การสาธิตเพียงเล็กน้อย แหล่งที่มา

ในระหว่างการเปลี่ยน วิดีโอจะค้าง จากนั้นวิดีโอที่กำลังเล่นจะค่อยๆ เลือนหายไป เนื่องจาก ::view-transition-old(video) เป็นภาพหน้าจอของมุมมองเก่า ในขณะที่ ::view-transition-new(video) เป็นรูปภาพที่เผยแพร่อยู่ของมุมมองใหม่

คุณอาจแก้ไขปัญหานี้ได้ แต่ก่อนอื่นให้ถามตัวเองว่าคุ้มค่าหรือไม่ หากคุณไม่เห็น "ปัญหา" เมื่อการเปลี่ยนเล่นด้วยความเร็วปกติ เราจะไม่เปลี่ยนแปลงปัญหาดังกล่าว

หากคุณต้องการแก้ไขจริงๆ ก็ไม่ต้องแสดง ::view-transition-old(video) ให้สลับไปที่ ::view-transition-new(video) โดยตรง ซึ่งทำได้โดยลบล้างรูปแบบและภาพเคลื่อนไหวเริ่มต้น ดังนี้

::view-transition-old(video) {
  /* Don't show the frozen old view */
  display: none;
}

::view-transition-new(video) {
  /* Don't fade the new view in */
  animation: none;
}

เพียงเท่านี้ก็เรียบร้อยแล้ว

วิดีโอเปลี่ยน ช้าลง การสาธิตเพียงเล็กน้อย แหล่งที่มา

ตอนนี้วิดีโอจะเล่นตลอดการเปลี่ยนผ่าน


สร้างภาพเคลื่อนไหวด้วย JavaScript

จนถึงตอนนี้ การเปลี่ยนทั้งหมดได้รับการกำหนดโดยใช้ CSS แต่บางครั้ง CSS ก็ยังไม่เพียงพอ

การเปลี่ยนแวดวง การสาธิตเพียงเล็กน้อย แหล่งที่มา

การเปลี่ยนแปลงนี้มี 2 - 3 ส่วนที่ไม่สามารถทำได้หากใช้ CSS เพียงอย่างเดียว

  • ภาพเคลื่อนไหวจะเริ่มจากตำแหน่งการคลิก
  • ภาพเคลื่อนไหวจะสิ้นสุดด้วยวงกลมที่มีรัศมีอยู่ในมุมที่ไกลที่สุด แต่หวังว่าจะทําได้กับ CSS ในอนาคต

โชคดีที่คุณสามารถสร้างทรานซิชันโดยใช้ Web Animation API ได้

let lastClick;
addEventListener('click', event => (lastClick = event));

function spaNavigate(data) {
  // Fallback for browsers that don't support this API:
  if (!document.startViewTransition) {
    updateTheDOMSomehow(data);
    return;
  }

  // Get the click position, or fallback to the middle of the screen
  const x = lastClick?.clientX ?? innerWidth / 2;
  const y = lastClick?.clientY ?? innerHeight / 2;
  // Get the distance to the furthest corner
  const endRadius = Math.hypot(
    Math.max(x, innerWidth - x),
    Math.max(y, innerHeight - y)
  );

  // With a transition:
  const transition = document.startViewTransition(() => {
    updateTheDOMSomehow(data);
  });

  // Wait for the pseudo-elements to be created:
  transition.ready.then(() => {
    // Animate the root's new view
    document.documentElement.animate(
      {
        clipPath: [
          `circle(0 at ${x}px ${y}px)`,
          `circle(${endRadius}px at ${x}px ${y}px)`,
        ],
      },
      {
        duration: 500,
        easing: 'ease-in',
        // Specify which pseudo-element to animate
        pseudoElement: '::view-transition-new(root)',
      }
    );
  });
}

ตัวอย่างนี้ใช้ transition.ready ซึ่งเป็นสัญญาที่จะแก้ไขเมื่อสร้างองค์ประกอบจำลองของการเปลี่ยนเรียบร้อยแล้ว พร็อพเพอร์ตี้อื่นๆ ของออบเจ็กต์นี้รวมอยู่ในข้อมูลอ้างอิง API


การเปลี่ยนฉากเพื่อเป็นการเพิ่มประสิทธิภาพ

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

หากต้องการถือว่าการเปลี่ยนนั้นเป็นการเพิ่มประสิทธิภาพ โปรดอย่าใช้สัญญาการเปลี่ยนในลักษณะที่จะทำให้แอปของคุณแสดงผลหากการเปลี่ยนนั้นล้มเหลว

ไม่ควรทำ
async function switchView(data) {
  // Fallback for browsers that don't support this API:
  if (!document.startViewTransition) {
    await updateTheDOM(data);
    return;
  }

  const transition = document.startViewTransition(async () => {
    await updateTheDOM(data);
  });

  await transition.ready;

  document.documentElement.animate(
    {
      clipPath: [`inset(50%)`, `inset(0)`],
    },
    {
      duration: 500,
      easing: 'ease-in',
      pseudoElement: '::view-transition-new(root)',
    }
  );
}

ปัญหาสำหรับตัวอย่างนี้คือ switchView() จะปฏิเสธหากการเปลี่ยนเข้าถึงสถานะ ready ไม่ได้ แต่ไม่ได้หมายความว่าการเปลี่ยนมุมมองล้มเหลว DOM อาจอัปเดตเรียบร้อยแล้ว แต่มี view-transition-name ที่ซ้ำกัน ระบบจึงข้ามการเปลี่ยนนี้

ให้ดำเนินการต่อไปนี้แทน

ควรทำ
async function switchView(data) {
  // Fallback for browsers that don't support this API:
  if (!document.startViewTransition) {
    await updateTheDOM(data);
    return;
  }

  const transition = document.startViewTransition(async () => {
    await updateTheDOM(data);
  });

  animateFromMiddle(transition);

  await transition.updateCallbackDone;
}

async function animateFromMiddle(transition) {
  try {
    await transition.ready;

    document.documentElement.animate(
      {
        clipPath: [`inset(50%)`, `inset(0)`],
      },
      {
        duration: 500,
        easing: 'ease-in',
        pseudoElement: '::view-transition-new(root)',
      }
    );
  } catch (err) {
    // You might want to log this error, but it shouldn't break the app
  }
}

ตัวอย่างนี้ใช้ transition.updateCallbackDone เพื่อรอการอัปเดต DOM และปฏิเสธหากการอัปเดตล้มเหลว switchView จะไม่ปฏิเสธอีกต่อไปหากการเปลี่ยนล้มเหลว แต่จะแก้ไขเมื่อการอัปเดต DOM เสร็จสมบูรณ์ และจะปฏิเสธหากล้มเหลว

หากต้องการให้ switchView แก้ไขเมื่อ "ตกลง" มุมมองใหม่เรียบร้อยแล้ว เช่น เมื่อการเปลี่ยนภาพเคลื่อนไหวเสร็จสมบูรณ์หรือข้ามไปสิ้นสุดการดำเนินการ ให้แทนที่ transition.updateCallbackDone ด้วย transition.finished


ไม่ใช่ Polyfill แต่...

ซึ่งไม่ใช่ฟีเจอร์ที่จะนำมาใช้ทำโพลีฟิลได้ง่ายๆ อย่างไรก็ตาม ฟังก์ชันตัวช่วยนี้จะทำให้การทำงานง่ายขึ้นมากในเบราว์เซอร์ที่ไม่รองรับการเปลี่ยนมุมมอง:

function transitionHelper({
  skipTransition = false,
  types = [],
  update,
}) {

  const unsupported = (error) => {
    const updateCallbackDone = Promise.resolve(update()).then(() => {});

    return {
      ready: Promise.reject(Error(error)),
      updateCallbackDone,
      finished: updateCallbackDone,
      skipTransition: () => {},
      types,
    };
  }

  if (skipTransition || !document.startViewTransition) {
    return unsupported('View Transitions are not supported in this browser');
  }

  try {
    const transition = document.startViewTransition({
      update,
      types,
    });

    return transition;
  } catch (e) {
    return unsupported('View Transitions with types are not supported in this browser');
  }
}

และนำมาใช้ได้ดังนี้

function spaNavigate(data) {
  const types = isBackNavigation ? ['back-transition'] : [];

  const transition = transitionHelper({
    update() {
      updateTheDOMSomehow(data);
    },
    types,
  });

  // …
}

ในเบราว์เซอร์ที่ไม่รองรับการเปลี่ยนมุมมอง ระบบจะยังคงเรียกใช้ updateDOM แต่จะไม่มีการเปลี่ยนเป็นภาพเคลื่อนไหว

นอกจากนี้ คุณยังระบุclassNamesบางส่วนเพื่อเพิ่มลงใน <html> ในช่วงการเปลี่ยนผ่านได้ ซึ่งจะช่วยให้เปลี่ยนการเปลี่ยนผ่านตามประเภทของการนำทางได้ง่ายขึ้น

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


การทำงานกับเฟรมเวิร์ก

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

  • รีแอค - คีย์ในที่นี้คือ flushSync ซึ่งจะใช้ชุดการเปลี่ยนแปลงสถานะแบบพร้อมกัน ใช่ เรามีคำเตือนสำคัญเกี่ยวกับการใช้ API นั้น แต่ Dan Abramov ยืนยันว่าเหมาะสมสำหรับกรณีนี้ เช่นเคยสำหรับ React และโค้ดอะซิงโครนัส เมื่อใช้สัญญาต่างๆ ที่ startViewTransition แสดงผล โปรดตรวจสอบว่าโค้ดกำลังทำงานด้วยสถานะที่ถูกต้อง
  • Vue.js - คีย์ในที่นี้คือ nextTick ซึ่งจะทํางานเมื่อ DOM ได้รับการอัปเดตแล้ว
  • Svelte - คล้ายกับ Vue มาก แต่วิธีการที่จะรอการเปลี่ยนแปลงครั้งถัดไปคือ tick
  • Lit - คีย์ในที่นี้คือ this.updateComplete คำมั่นสัญญาภายในคอมโพเนนต์ ซึ่งจะดำเนินการอีกครั้งเมื่อ DOM ได้รับการอัปเดตแล้ว
  • Angular - คีย์ในที่นี้คือ applicationRef.tick ซึ่งจะล้างการเปลี่ยนแปลง DOM ที่รอดำเนินการ ตั้งแต่ Angular เวอร์ชัน 17 เป็นต้นไป คุณสามารถใช้ withViewTransitions ที่มาพร้อมกับ @angular/router ได้

เอกสารอ้างอิง API

const viewTransition = document.startViewTransition(update)

เริ่มViewTransitionใหม่

update เป็นฟังก์ชันที่จะเรียกใช้เมื่อมีการบันทึกสถานะปัจจุบันของเอกสาร

จากนั้น เมื่อ updateCallback ตอบกลับสิ่งที่สัญญาไว้จะทำได้สำเร็จ การเปลี่ยนจะเริ่มในเฟรมถัดไป หากคำมั่นสัญญาที่ updateCallback ส่งกลับมาถูกปฏิเสธ การเปลี่ยนผ่านจะถูกยกเลิก

const viewTransition = document.startViewTransition({ update, types })

เริ่มต้น ViewTransition ใหม่ด้วยประเภทที่ระบุ

update จะถูกเรียกเมื่อบันทึกสถานะปัจจุบันของเอกสารแล้ว

types กำหนดประเภทที่ใช้งานอยู่สำหรับการเปลี่ยนเมื่อบันทึกหรือทำการเปลี่ยน เนื่องจากว่างเปล่าในตอนแรก ดู viewTransition.types เพิ่มเติมด้านล่างสำหรับข้อมูลเพิ่มเติม

สมาชิกอินสแตนซ์ของ ViewTransition:

viewTransition.updateCallbackDone

สัญญาที่จะบรรลุผลเมื่อสัญญาที่ส่งกลับมาโดย updateCallback ตอบสนองหรือถูกปฏิเสธเมื่อปฏิเสธ

View Transition API จะรวมการเปลี่ยนแปลง DOM และสร้างการเปลี่ยน อย่างไรก็ตาม บางครั้งคุณอาจไม่ได้สนใจเรื่องความสําเร็จหรือความล้มเหลวของภาพเคลื่อนไหวการเปลี่ยน คุณแค่อยากรู้ว่าการเปลี่ยนแปลง DOM จะเกิดขึ้นหรือไม่และเมื่อใด updateCallbackDone มีไว้สำหรับกรณีการใช้งานดังกล่าว

viewTransition.ready

คำสัญญาที่จะมีผลเมื่อมีการสร้างองค์ประกอบเทียมสำหรับการเปลี่ยน และภาพเคลื่อนไหวกำลังจะเริ่มต้น

และปฏิเสธหากการเปลี่ยนไม่สามารถเริ่มต้นได้ ซึ่งอาจเป็นเพราะการกำหนดค่าที่ไม่ถูกต้อง เช่น view-transition-name ที่ซ้ำกัน หรือหาก updateCallback ส่งคำสัญญาที่ถูกปฏิเสธกลับมา

วิธีนี้มีประโยชน์สำหรับการทำให้องค์ประกอบเทียมของการเปลี่ยนเคลื่อนไหวด้วย JavaScript

viewTransition.finished

คำมั่นสัญญาที่จะบรรลุผลเมื่อสถานะสิ้นสุดจะแสดงและโต้ตอบกับผู้ใช้ได้อย่างเต็มที่

จะปฏิเสธก็ต่อเมื่อ updateCallback แสดงผลสัญญาที่ถูกปฏิเสธ เนื่องจากข้อความนี้บ่งบอกว่าไม่มีการสร้างสถานะสิ้นสุด

มิฉะนั้น หากการเปลี่ยนเริ่มต้นไม่สําเร็จหรือข้ามในระหว่างการเปลี่ยน สถานะสิ้นสุดจะยังคงเป็นสถานะสุดท้าย ดังนั้น finished จะดําเนินการต่อ

viewTransition.types

ออบเจ็กต์คล้าย Set ที่มีประเภทการเปลี่ยนของมุมมองแอ็กทีฟ หากต้องการจัดการรายการ ให้ใช้เมธอดอินสแตนซ์ clear(), add() และ delete()

หากต้องการตอบกลับประเภทที่เฉพาะเจาะจงใน CSS ให้ใช้ตัวเลือกคลาส Pseudo อย่าง :active-view-transition-type(type) ในรูทการเปลี่ยน

ระบบจะล้างประเภทโดยอัตโนมัติเมื่อเปลี่ยนมุมมอง

viewTransition.skipTransition()

ข้ามส่วนของภาพเคลื่อนไหวในการเปลี่ยน

การดำเนินการนี้จะไม่ข้ามการเรียกใช้ updateCallback เนื่องจากการเปลี่ยนแปลง DOM จะแยกต่างหากจากการเปลี่ยน


รูปแบบเริ่มต้นและการอ้างอิงการเปลี่ยน

::view-transition
องค์ประกอบจำลองระดับรูทซึ่งเติมลงในวิวพอร์ตและมี ::view-transition-group แต่ละรายการ
::view-transition-group

วางตำแหน่งอย่างเหมาะเจาะ

การเปลี่ยน width และ height ระหว่างสถานะ "ก่อน" และ "หลัง"

การเปลี่ยน transform ระหว่างสี่เหลี่ยมจัตุรัสในวิวพอร์ต "ก่อน" และ "หลัง"

::view-transition-image-pair

ตำแหน่งที่ดีที่สุดในการเติมเต็มกลุ่ม

มี isolation: isolate เพื่อจำกัดผลกระทบของ mix-blend-mode ต่อมุมมองเก่าและใหม่

::view-transition-newและ::view-transition-old

วางตำแหน่งสัมบูรณ์ไว้ที่ด้านซ้ายบนของ Wrapper

ใช้ความกว้างกลุ่ม 100% แต่มีความสูงอัตโนมัติ จึงจะคงสัดส่วนภาพไว้แทนการเติมข้อมูลกลุ่ม

มี mix-blend-mode: plus-lighter เพื่อทำให้เกิดการจางลงอย่างแท้จริง

มุมมองเดิมเปลี่ยนจาก opacity: 1 เป็น opacity: 0 มุมมองใหม่จะเปลี่ยนจาก opacity: 0 เป็น opacity: 1


ความคิดเห็น

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

หากคุณพบข้อบกพร่อง ให้รายงานข้อบกพร่องของ Chromium แทน