ป้องกันปัญหาการตัด (และอื่นๆ) ในการเปลี่ยนฉากโดยใช้กลุ่มการเปลี่ยนฉากที่ซ้อนกัน

เผยแพร่เมื่อ: 22 ก.ย. 2025

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

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

  • การตัด (overflow, clip-path, border-radius): การตัดจะส่งผลต่อองค์ประกอบย่อย ซึ่งหมายความว่าองค์ประกอบข้างเคียงของกลุ่มการเปลี่ยนมุมมองจะตัดกันเองไม่ได้
  • opacity, mask-image และ filter: ในทำนองเดียวกัน เอฟเฟกต์เหล่านี้ออกแบบมาเพื่อทำงานกับรูปภาพที่แรสเตอร์อย่างสมบูรณ์ของต้นไม้ ซึ่งส่งผลต่อองค์ประกอบย่อย แทนที่จะส่งผลต่อแต่ละรายการแยกกัน
  • การเปลี่ยนรูปแบบ 3 มิติ (transform-style, transform, perspective): หากต้องการแสดงภาพเคลื่อนไหวของการเปลี่ยนรูปแบบ 3 มิติอย่างเต็มรูปแบบ คุณต้องคงลำดับชั้นบางอย่างไว้

ตัวอย่างต่อไปนี้แสดงแผนผังเทียมแบบแบนราบ โดยมีองค์ประกอบที่ถูกตัดโดยระดับบนสุดในแผนผัง DOM องค์ประกอบเหล่านี้จะสูญเสียการตัดคลิปในระหว่างการเปลี่ยนมุมมอง ส่งผลให้เอฟเฟกต์ภาพเสียหาย

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

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

Browser Support

  • Chrome: 140.
  • Edge: 140.
  • Firefox: not supported.
  • Safari: not supported.

จากต้นไม้เทียมแบบแบนไปเป็นต้นไม้เทียมแบบซ้อนกัน

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

การสาธิตสด

การบันทึกเดโม

Demo Recording (Slowed down)

หากดูการสาธิตอย่างละเอียด คุณจะเห็นว่ามีปัญหาเกี่ยวกับการเปลี่ยน แม้ว่าย่อหน้าที่มีคำอธิบายจะเป็นองค์ประกอบย่อยขององค์ประกอบ <dialog> ใน DOM แต่ข้อความจะไม่ถูกตัดโดยกล่อง <dialog> ในระหว่างการเปลี่ยน

<dialog id="info_bramus" closedby="any">
  <h2><img alt="…" class="avatar" height="96" width="96" src="avatar_bramus.jpg"> <span>Bramus</span></h2>
  <p>Bramus is …</p>
  <p>…</p>
</dialog>

การใช้ overflow: clip ใน <dialog> ก็ไม่ช่วยอะไรเช่นกัน

ปัญหาคือวิธีที่การเปลี่ยนมุมมองสร้างและแสดงผลต้นไม้เทียม

  • ใน Pseudo-Tree สแนปชอตทั้งหมดจะเป็นพี่น้องกันโดยค่าเริ่มต้น
  • ระบบจะแสดงผล Pseudo-Tree ในองค์ประกอบเทียม ::view-transition ซึ่งแสดงผลที่ด้านบนของเอกสารทั้งหมด

สำหรับเดโมนี้โดยเฉพาะ แผนผัง DOM จะมีลักษณะดังนี้

html
  ├─ ::view-transition
  │  ├─ ::view-transition-group(card)
  │  │  └─ ::view-transition-image-pair(card)
  │  │     ├─ ::view-transition-old(card)
  │  │     └─ ::view-transition-new(card)
  │  ├─ ::view-transition-group(name)
  │  │  └─ ::view-transition-image-pair(name)
  │  │     ├─ ::view-transition-old(name)
  │  │     └─ ::view-transition-new(name)
  │  ├─ ::view-transition-group(avatar)
  │  │  └─ ::view-transition-image-pair(avatar)
  │  │     ├─ ::view-transition-old(avatar)
  │  │     └─ ::view-transition-new(avatar)
  │  ├─ ::view-transition-group(paragraph1.text)
  │  │  └─ ::view-transition-image-pair(paragraph1.text)
  │  │     └─ ::view-transition-new(paragraph1.text)
  │  └─ ::view-transition-group(paragraph2.text)
  │     └─ ::view-transition-image-pair(paragraph2.text)
  │        └─ ::view-transition-new(paragraph2.text)
  ├─ head
  └─ body
        └─ …

เนื่องจากองค์ประกอบเทียม ::view-transition-group(.text) เป็นพี่น้องที่สืบทอดต่อจากองค์ประกอบเทียม ::view-transition-group(card) จึงได้รับการลงสีที่ด้านบนของการ์ด

หากต้องการให้ ::view-transition-group(card) ตัด ::view-transition-group(.text) องค์ประกอบเทียม ::view-transition-group(.text) ควรเป็นองค์ประกอบย่อยของ ::view-transition-group(card) สำหรับกรณีนี้ ให้ใช้ view-transition-group ซึ่งช่วยให้คุณกำหนด "กลุ่มหลัก" สำหรับองค์ประกอบจำลอง ::view-transition-group() ที่สร้างขึ้นได้

หากต้องการเปลี่ยนกลุ่มหลัก คุณมี 2 ตัวเลือกดังนี้

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

ดังนั้นสำหรับการสาธิตนี้ หากต้องการใช้กลุ่มการเปลี่ยนมุมมองที่ฝังอยู่ โค้ดจะเป็นดังนี้

button.clicked,
dialog {
  view-transition-group: contain;
}

หรือ

button.clicked,
dialog *,
  view-transition-group: nearest;
}

เมื่อมีโค้ดนี้แล้ว ตอนนี้คลาสเทียม ::view-transition-group(.text) จะซ้อนอยู่ภายในคลาสเทียม ::view-transition-group(card) ซึ่งทำได้ใน ::view-transition-group-children(…) เทียมเพิ่มเติม ซึ่งจะเก็บเทียมที่ฝังทั้งหมดไว้ด้วยกัน ดังนี้

html
  ├─ ::view-transition
  │  ├─ ::view-transition-group(card)
  │  │  ├─::view-transition-group-children(card)
  │  │  │ ├─ ::view-transition-group(paragraph1.text)
  │  │  │ │  └─ ::view-transition-image-pair(paragraph1.text)
  │  │  │ │     └─ ::view-transition-new(paragraph1.text)
  │  │  │ └─ ::view-transition-group(paragraph2.text)
  │  │  │    └─ ::view-transition-image-pair(paragraph2.text)
  │  │  │       └─ ::view-transition-new(paragraph2.text)
  │  │  └─ ::view-transition-image-pair(card)
  │  │     ├─ ::view-transition-old(card)
  │  │     └─ ::view-transition-new(card)
  │  ├─ ::view-transition-group(name)
  │  │  └─ ::view-transition-image-pair(name)
  │  │     ├─ ::view-transition-old(name)
  │  │     └─ ::view-transition-new(name)
  │  └─ ::view-transition-group(avatar)
  │     └─ ::view-transition-image-pair(avatar)
  │        ├─ ::view-transition-old(avatar)
  │        └─ ::view-transition-new(avatar)
  ├─ head
  └─ body
        └─ …

สุดท้าย หากต้องการให้ ::view-transition-group(card) pseudo ตัดย่อหน้า ให้ใช้ overflow: clip กับ ::view-transition-group-children(card) pseudo ดังนี้

::view-transition-group-children(card) {
  overflow: clip;
}

ผลลัพธ์ที่ได้มีดังนี้

การสาธิตสด

การบันทึกเดโม

Demo Recording (Slowed down)

::view-transition-group-children เทียมจะแสดงก็ต่อเมื่อใช้กลุ่มที่ซ้อนกันเท่านั้น โดยมีขนาดเท่ากับกล่องเส้นขอบขององค์ประกอบเดิม และมีเส้นขอบโปร่งใสที่มีรูปร่างและความหนาของเส้นขอบเหมือนกับองค์ประกอบที่สร้างองค์ประกอบจำลอง ซึ่งก็คือ card ในตัวอย่างก่อนหน้านี้

การตัดคลิปและอื่นๆ

ระบบจะใช้กลุ่มการเปลี่ยนมุมมองที่ฝังไว้ในตำแหน่งอื่นๆ นอกเหนือจากเอฟเฟกต์การตัดคลิป อีกตัวอย่างหนึ่งคือเอฟเฟกต์ 3 มิติ ในการสาธิตต่อไปนี้ จะมีตัวเลือกในการหมุนการ์ดแบบ 3 มิติระหว่างการเปลี่ยนฉาก

html:active-view-transition-type(open) {
    &::view-transition-old(card) {
        animation-name: rotate-out;
    }
    &::view-transition-new(card) {
        animation-name: rotate-in;
    }
}
html:active-view-transition-type(close) {
    &::view-transition-old(card) {
        animation-name: rotate-in;
    }
    &::view-transition-new(card) {
        animation-name: rotate-out;
    }
}

หากไม่มีกลุ่มการเปลี่ยนมุมมองที่ฝัง รูปโปรไฟล์และชื่อจะไม่หมุนไปพร้อมกับการ์ด

การสาธิตสด

การบันทึกเดโม

Demo Recording (Slowed down)

การฝังอวาตาร์และชื่อเทียมไว้ในการ์ดจะช่วยคืนค่าเอฟเฟกต์ 3 มิติได้ แต่นั่นไม่ใช่สิ่งเดียวที่คุณต้องทำ นอกจากการหมุนเวียน ::view-transition-old(card) และ ::view-transition-new(card) pseudos แล้ว คุณยังต้องหมุนเวียน ::view-transition-group-children(card) ด้วย

html:active-view-transition-type(open) {
    &::view-transition-group-children(card) {
        animation: rotate-in var(--duration) ease;
        backface-visibility: hidden;
    }
}
html:active-view-transition-type(close) {
    &::view-transition-group-children(card) {
        animation: rotate-out var(--duration) ease;
        backface-visibility: hidden;
    }
}

การสาธิตสด

การบันทึกเดโม

Demo Recording (Slowed down)

การสาธิตเพิ่มเติม

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

การสาธิตสด

การบันทึกเดโม

สิ่งที่น่าสนใจเกี่ยวกับการสาธิตนี้คือ ::view-transition-group(.card) pseudos ทั้งหมดจะซ้อนกันอยู่ภายในและถูกตัดโดย ::view-transition-group(cards) pseudo ระดับบนสุด ส่วน #targeted-card จะไม่รวมอยู่ด้วยเนื่องจากภาพเคลื่อนไหวเข้า/ออกไม่ควรถูกตัดโดย ::view-transition-group(cards)

/* The .cards wrapper contains all children */
.cards {
  view-transition-name: cards;
  view-transition-group: contain;
}

/* Contents that bleed out get clipped */
&::view-transition-group-children(cards) {
  overflow: clip;
}

/* Each card is given a v-t-name and v-t-class */
.card {
  view-transition-name: match-element;
  view-transition-class: card;
}

/* The targeted card is given a unique name (to style the pseudo differently)
   and shouldn't be contained by the ::view-transition-group-children(cards) pseudo */
#targeted-card {
  view-transition-name: targeted-card;
  view-transition-group: none;
}

สรุป

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

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