Zapobieganie problemom z przycinaniem (i innym) podczas przejść między widokami dzięki użyciu zagnieżdżonych grup przejść między widokami

Opublikowano: 22 września 2025 r.

Gdy rozpoczniesz przejście widoku, przeglądarka automatycznie zrobi zrzuty elementów oznaczonych atrybutem view-transition-name przed i po zmianie. Te migawki są renderowane w drzewie pseudoelementów. Domyślnie wygenerowane drzewo jest „płaskie”. Oznacza to, że pierwotna hierarchia w DOM zostaje utracona, a wszystkie przechwycone grupy przejść widoku są elementami równorzędnymi w ramach jednego pseudoelementu ::view-transition.

Takie podejście sprawdza się w wielu przypadkach, ale w niektórych sytuacjach związanych ze stylem nie można go zastosować. Oto przykłady efektów, które mogą mieć nieoczekiwany efekt wizualny w płaskim drzewie:

  • Przycinanie (overflow, clip-path, border-radius): przycinanie wpływa na elementy podrzędne, co oznacza, że elementy równorzędne w grupie przejścia widoku nie mogą się wzajemnie przycinać.
  • opacity, mask-image i filter: te efekty są zaprojektowane tak, aby działać na w pełni zrasteryzowanym obrazie drzewa, wpływając na elementy podrzędne, a nie na poszczególne elementy.
  • Przekształcenia 3D (transform-style, transform, perspective): aby wyświetlić pełny zakres animacji przekształceń 3D, należy zachować pewną hierarchię.

Poniższy przykład przedstawia płaskie pseudodrzewo z elementami, które są przycinane przez element nadrzędny w drzewie DOM. Podczas przejścia widoku te elementy tracą przycinanie, co powoduje nieprawidłowy efekt wizualny.

Nagranie z uszkodzonym efektem przycinania podczas przejścia widoku. Tekst powinien być przycięty przez okno, ale tak się nie dzieje. Czas animacji jest spowolniony, aby wyolbrzymić efekt.

Zagnieżdżone grupy przejść widoku to rozszerzenie przejść widoku, które umożliwia zagnieżdżanie ::view-transition-group pseudo-elementów w sobie. Gdy grupy przejścia widoku są zagnieżdżone, podczas przejścia można przywrócić efekty, takie jak przycinanie.

Browser Support

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

Od płaskiego pseudodrzewa do zagnieżdżonego pseudodrzewa

W poniższym demo możesz kliknąć awatar osoby, aby wyświetlić więcej informacji na jej temat. Animacje są obsługiwane przez przejście widoku w tym samym dokumencie, które przekształca kliknięty przycisk w okno, przesuwa awatar i imię po ekranie oraz przesuwa akapity z okna w górę lub w dół.

Prezentacja na żywo

Nagranie demonstracyjne

Nagranie demonstracyjne (zwolnione)

Jeśli przyjrzysz się uważnie wersji demonstracyjnej, zauważysz problem z przejściem: mimo że akapity z opisem są elementami podrzędnymi elementu <dialog> w DOM, tekst nie jest przycinany przez pole elementu <dialog> podczas przejścia:

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

Zastosowanie overflow: clip na <dialog> również nic nie daje.

Problem polega na tym, jak przejścia widoku tworzą i renderują swoje pseudodrzewo:

  • W pseudo-drzewie wszystkie migawki są domyślnie rodzeństwem.
  • Pseudo-drzewo jest renderowane w ::view-transition pseudo-elemencie, który jest renderowany na wierzchu całego dokumentu.

W tym przypadku drzewo DOM wygląda tak:

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

Ponieważ pseudoklasy ::view-transition-group(.text) są kolejnymi elementami po pseudoklasie ::view-transition-group(card), są rysowane na wierzchu karty.

Aby klip ::view-transition-group(card) miał ::view-transition-group(.text), pseudoselektory ::view-transition-group(.text) muszą być elementami podrzędnymi elementu ::view-transition-group(card). W tym celu użyj view-transition-group, który umożliwia przypisanie „grupy nadrzędnej” do wygenerowanego pseudoelementu ::view-transition-group().

Aby zmienić grupę nadrzędną, masz 2 możliwości:

  • W elemencie nadrzędnym ustaw atrybut view-transition-group na contain, aby zawierał wszystkie elementy podrzędne z atrybutem view-transition-name.
  • We wszystkich elementach podrzędnych ustaw view-transition-group na view-transition-name elementu nadrzędnego. Możesz też użyć znaku nearest, aby kierować reklamy na najbliższą grupę nadrzędną.

W tym przykładzie, aby użyć zagnieżdżonych grup przejść widoku, kod wygląda tak:

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

Lub

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

Po dodaniu tego kodu pseudoklasy ::view-transition-group(.text) zostaną zagnieżdżone w pseudoklasie ::view-transition-group(card). Odbywa się to w dodatkowym pseudoselektorze ::view-transition-group-children(…), który utrzymuje wszystkie zagnieżdżone pseudoselektory razem:

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

Aby element ::view-transition-group(card) przycinał akapity, zastosuj overflow: clip do elementu ::view-transition-group-children(card):

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

Wynik jest następujący:

Prezentacja na żywo

Nagranie demonstracyjne

Nagranie demonstracyjne (zwolnione)

Pseudoklasa ::view-transition-group-children występuje tylko wtedy, gdy używane są grupy zagnieżdżone. Ma rozmiar border-box oryginalnego elementu i przezroczyste obramowanie o takim samym kształcie i grubości jak element, który wygenerował pseudoelement – card w poprzednim przykładzie.

Wycinanie i inne funkcje

Zagnieżdżone grupy przejść widoku są używane w innych miejscach niż efekty przycinania. Innym przykładem są efekty 3D. W poniższym filmie demonstracyjnym podczas przejścia można obracać kartę w 3D.

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

Bez zagnieżdżonych grup przejść widoku awatar i imię nie obracają się wraz z kartą.

Prezentacja na żywo

Nagranie demonstracyjne

Nagranie demonstracyjne (zwolnione)

Umieszczenie pseudoklas awatara i nazwy wewnątrz karty przywraca efekt 3D. To jednak nie wszystko. Oprócz obracania pseudoznaków ::view-transition-old(card)::view-transition-new(card) musisz też obracać pseudoznak ::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;
    }
}

Prezentacja na żywo

Nagranie demonstracyjne

Nagranie demonstracyjne (zwolnione)

Więcej wersji demonstracyjnych

W poniższym przykładzie zagnieżdżone grupy przejść widoku są używane, aby karty były przycinane przez element przewijany nadrzędny. Za pomocą dostępnych elementów sterujących możesz włączać i wyłączać korzystanie z zagnieżdżonych grup przejść widoku.

Prezentacja na żywo

Nagranie demonstracyjne

Ciekawostką w tym przykładzie jest to, że wszystkie pseudoelementy ::view-transition-group(.card) są zagnieżdżone w pseudoelemencie ::view-transition-group(cards) elementu nadrzędnego i przez niego przycinane. Element #targeted-card jest wykluczony, ponieważ jego animacja wejścia/wyjścia nie powinna być przycinana przez element ::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;
}

Podsumowanie

Zagnieżdżone przejścia widoku umożliwiają zachowanie części topologii drzewa DOM podczas tworzenia pseudoelementów. Umożliwia to uzyskanie różnych efektów, które wcześniej nie były możliwe w przypadku przejść widoku. Niektóre z nich opisaliśmy tutaj.

Zagnieżdżanie zmienia model tworzenia przejść widoku i jest przeznaczone do tworzenia zaawansowanych efektów. Jak wspomnieliśmy, przejścia widoku w zakresie elementu mogą też osiągnąć podzbiór efektów za pomocą prostszego modelu. Zachęcamy do wypróbowania obu funkcji, aby zdecydować, która z nich najlepiej odpowiada Twoim potrzebom.