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

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

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

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

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

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

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

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

Browser Support

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

จากโครงสร้างแบบลำดับชั้นเทียมแบบแบนเป็นโครงสร้างแบบลำดับชั้นเทียมแบบซ้อนกัน

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

การสาธิตการใช้งานแบบสด

การบันทึกการสาธิต

การบันทึกการสาธิต (ช้าลง)

หากดูการสาธิตอย่างละเอียด คุณจะเห็นว่าการเปลี่ยนผ่านมีปัญหา แม้ว่าย่อหน้าที่มีคำอธิบายจะเป็นองค์ประกอบย่อยขององค์ประกอบ <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> ก็ไม่ได้ผลเช่นกัน

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

  • ในโครงสร้างแบบลำดับชั้นเสมือน สแนปชอตทั้งหมดจะเป็นองค์ประกอบระดับเดียวกันโดยค่าเริ่มต้น
  • ระบบจะแสดงผลโครงสร้างเสมือนใน::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-image-pair(card)
  │  │  │  ├─ ::view-transition-old(card)
  │  │  │  └─ ::view-transition-new(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-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) ตัดย่อหน้า ให้ใช้ overflow: clip กับ ::view-transition-group-children(card) ดังนี้

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

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

การสาธิตการใช้งานแบบสด

การบันทึกการสาธิต

การบันทึกการสาธิต (ช้าลง)

::view-transition-group-children pseudo จะแสดงเมื่อใช้กลุ่มที่ซ้อนกันเท่านั้น โดยมีขนาดเท่ากับ border-box ขององค์ประกอบเดิม และมีเส้นขอบโปร่งใสที่มีรูปร่างและความหนาของเส้นขอบเหมือนกับองค์ประกอบที่สร้างองค์ประกอบเสมือน ซึ่งก็คือ 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;
    }
}

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

การสาธิตการใช้งานแบบสด

การบันทึกการสาธิต

การบันทึกการสาธิต (ช้าลง)

การซ้อนตัวอวตารและชื่อเสมือนไว้ในการ์ดจะช่วยให้เอฟเฟกต์ 3 มิติกลับมาทำงานได้ แต่คุณต้องทำมากกว่านั้น นอกจากการหมุน::view-transition-old(card)และ::view-transition-new(card)แบบหลอกแล้ว คุณยังต้องหมุน::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;
    }
}

การสาธิตการใช้งานแบบสด

การบันทึกการสาธิต

การบันทึกการสาธิต (ช้าลง)

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

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

การสาธิตการใช้งานแบบสด

การบันทึกการสาธิต

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

/* 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 ไว้ได้เมื่อสร้างองค์ประกอบเสมือน ซึ่งจะช่วยให้ใช้เอฟเฟกต์ต่างๆ ได้มากมายซึ่งก่อนหน้านี้ทำไม่ได้ด้วยการเปลี่ยนฉาก ซึ่งเราได้อธิบายบางส่วนไว้ที่นี่

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