Ngăn chặn các vấn đề về việc cắt nội dung (và nhiều vấn đề khác) trong các hiệu ứng chuyển đổi chế độ xem bằng cách sử dụng các nhóm hiệu ứng chuyển đổi chế độ xem lồng ghép

Ngày xuất bản: 22 tháng 9 năm 2025

Khi bạn bắt đầu một quá trình chuyển đổi khung hiển thị, trình duyệt sẽ tự động chụp nhanh trước và sau các phần tử được gắn thẻ view-transition-name. Các ảnh chụp nhanh này được kết xuất trong một cây phần tử giả. Theo mặc định, cây được tạo sẽ "phẳng". Điều này có nghĩa là hệ phân cấp ban đầu trong DOM bị mất và tất cả các nhóm chuyển đổi khung hiển thị đã ghi lại đều là đồng cấp trong một phần tử giả ::view-transition duy nhất.

Phương pháp cây phẳng này đủ cho nhiều trường hợp sử dụng, nhưng có một số trường hợp sử dụng kiểu không thể đạt được bằng phương pháp này. Sau đây là ví dụ về các hiệu ứng có thể có hiệu ứng hình ảnh không mong muốn trong cây phẳng:

  • Cắt (overflow, clip-path, border-radius): việc cắt ảnh hưởng đến các phần tử con của phần tử, tức là các phần tử đồng cấp của nhóm chuyển đổi khung hiển thị không thể cắt nhau.
  • opacity, mask-image và filter: tương tự, các hiệu ứng này được thiết kế để hoạt động trên hình ảnh được tạo điểm ảnh hoàn toàn của một cây, ảnh hưởng đến các phần tử con, thay vì ảnh hưởng đến từng mục riêng lẻ.
  • Biến đổi 3D (transform-style, transform, perspective): để hiển thị toàn bộ ảnh động biến đổi 3D, bạn cần duy trì một số hệ phân cấp.

Ví dụ sau đây cho thấy một cây giả phẳng, với các phần tử được cắt bớt bởi một đối tượng cấp trên trong cây DOM. Các phần tử này mất đi hiệu ứng cắt trong quá trình chuyển đổi khung hiển thị, dẫn đến hiệu ứng hình ảnh bị hỏng.

Bản ghi hiệu ứng cắt bị hỏng trong khi quá trình chuyển đổi khung hiển thị chạy. Văn bản phải được hộp thoại cắt bớt, nhưng không phải vậy. Thời gian ảnh động được làm chậm để phóng đại hiệu ứng.

Nhóm chuyển đổi khung hiển thị lồng nhau là một tiện ích cho hiệu ứng chuyển đổi khung hiển thị, cho phép bạn lồng các phần tử giả ::view-transition-group vào nhau. Khi các nhóm chuyển đổi khung hiển thị được lồng, bạn có thể khôi phục các hiệu ứng như cắt trong quá trình chuyển đổi.

Browser Support

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

Từ cây giả phẳng đến cây giả lồng nhau

Trong bản minh hoạ sau, bạn có thể nhấp vào hình đại diện của một người để xem thêm thông tin về người đó. Ảnh động được xử lý bằng hiệu ứng chuyển đổi khung hiển thị cùng tài liệu, biến nút được nhấp vào thành hộp thoại, di chuyển hình đại diện và tên trên màn hình, đồng thời trượt các đoạn văn bản từ hộp thoại lên hoặc xuống.

Minh hoạ trực tiếp

Bản thu thử

Bản thu thử (Giảm tốc độ)

Nếu xem kỹ bản minh hoạ, bạn sẽ thấy có vấn đề với hiệu ứng chuyển đổi: mặc dù các đoạn văn có nội dung mô tả là phần tử con của phần tử <dialog> trong DOM, nhưng văn bản không bị hộp của <dialog> cắt bớt trong quá trình chuyển đổi:

<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>

Việc áp dụng overflow: clip trên <dialog> cũng không có tác dụng gì.

Vấn đề nằm ở cách hiệu ứng chuyển đổi khung hiển thị tạo và kết xuất cây giả:

  • Trong cây giả, theo mặc định, tất cả ảnh chụp nhanh đều là thành phần đồng cấp của nhau.
  • Cây giả được kết xuất trong một phần tử giả ::view-transition, phần tử này kết xuất ở đầu toàn bộ tài liệu.

Cụ thể, đối với bản minh hoạ này, cây DOM sẽ có dạng như sau:

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
        └─ …

Vì các phần tử giả ::view-transition-group(.text) là phần tử đồng cấp kế thừa của phần tử giả ::view-transition-group(card), nên chúng được vẽ lên trên thẻ.

Để ::view-transition-group(card) cắt ::view-transition-group(.text), các phần tử giả ::view-transition-group(.text) phải là phần tử con của ::view-transition-group(card). Để làm việc này, hãy sử dụng view-transition-group để chỉ định một "nhóm mẹ" cho phần tử giả ::view-transition-group() được tạo.

Để thay đổi nhóm mẹ, bạn có hai lựa chọn:

  • Trên phần tử mẹ, hãy đặt view-transition-group thành contain để phần tử này chứa tất cả phần tử con có view-transition-name.
  • Trên tất cả thành phần con, hãy đặt view-transition-group thành view-transition-name của thành phần mẹ. Bạn cũng có thể sử dụng nearest để nhắm đến nhóm cấp trên gần nhất.

Vì vậy, đối với bản minh hoạ này, để sử dụng các nhóm chuyển đổi thành phần hiển thị lồng nhau, mã sẽ trở thành:

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

Hoặc

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

Với mã này, các phần tử giả ::view-transition-group(.text) hiện được lồng vào bên trong phần tử giả ::view-transition-group(card). Việc này được thực hiện trong một phần tử giả ::view-transition-group-children(…) bổ sung, giúp giữ tất cả các phần tử giả lồng nhau lại với nhau:

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
        └─ …

Cuối cùng, để giả lập ::view-transition-group(card) cắt bớt các đoạn văn, hãy áp dụng overflow: clip cho giả lập ::view-transition-group-children(card):

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

Kết quả sẽ như sau:

Minh hoạ trực tiếp

Bản thu thử

Bản thu thử (Giảm tốc độ)

Lớp giả ::view-transition-group-children chỉ xuất hiện khi sử dụng các nhóm lồng nhau. Phần tử này có kích thước bằng hộp đường viền của phần tử gốc và có đường viền trong suốt có cùng hình dạng và độ dày đường viền với phần tử đã tạo phần tử giả — card trong ví dụ trước.

Cắt và các tính năng khác

Các nhóm chuyển đổi khung hiển thị lồng nhau được dùng ở những nơi khác ngoài hiệu ứng cắt. Một ví dụ khác là hiệu ứng 3D. Trong bản minh hoạ sau, có một tuỳ chọn để xoay thẻ ở chế độ 3D trong quá trình chuyển đổi.

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;
    }
}

Nếu không có nhóm chuyển đổi khung hiển thị lồng nhau, hình đại diện và tên sẽ không xoay cùng với thẻ.

Minh hoạ trực tiếp

Bản thu thử

Bản thu thử (Giảm tốc độ)

Bằng cách lồng hình đại diện và tên giả vào bên trong thẻ, bạn có thể khôi phục hiệu ứng 3D. Nhưng đó không phải là việc duy nhất bạn cần làm. Ngoài việc xoay các phần tử giả ::view-transition-old(card) và ::view-transition-new(card), bạn cũng cần xoay phần tử giả ::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;
    }
}

Minh hoạ trực tiếp

Bản thu thử

Bản thu thử (Giảm tốc độ)

Bản minh hoạ khác

Trong ví dụ sau, các nhóm chuyển đổi khung hiển thị lồng nhau được dùng để đảm bảo các thẻ được cắt bớt theo trình cuộn gốc. Bạn có thể bật hoặc tắt việc sử dụng các nhóm chuyển đổi khung hiển thị lồng nhau bằng các chế độ điều khiển đi kèm.

Minh hoạ trực tiếp

Bản thu thử

Điều thú vị về bản minh hoạ này là tất cả phần tử giả ::view-transition-group(.card) đều được lồng vào bên trong và được cắt bớt bởi phần tử giả ::view-transition-group(cards) gốc. #targeted-card bị loại trừ vì ảnh động vào/ra của nó không được cắt bớt bởi ::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;
}

Tóm tắt

Hiệu ứng chuyển đổi thành phần hiển thị lồng nhau cho phép bạn giữ lại một số cấu trúc liên kết của cây DOM khi tạo các phần tử giả. Điều này giúp bạn có thể sử dụng nhiều hiệu ứng mà trước đây không thể thực hiện được bằng hiệu ứng chuyển đổi khung hiển thị, một số hiệu ứng trong số đó được mô tả tại đây.

Việc lồng ghép sẽ thay đổi mô hình về cách tạo hiệu ứng chuyển đổi khung hiển thị và được dùng để tạo hiệu ứng nâng cao. Như đã lưu ý, hiệu ứng chuyển đổi khung hiển thị trong phạm vi phần tử cũng có thể thực hiện một số hiệu ứng bằng mô hình đơn giản hơn. Bạn nên dùng thử cả hai tính năng để quyết định xem tính năng nào phù hợp nhất với nhu cầu của mình.