Как предотвратить обрезку и другие проблемы при переходах между представлениями с помощью вложенных групп переходов

Дата публикации: 22 сентября 2025 г.

Когда вы запускаете переход между представлениями, браузер автоматически делает снимки элементов, помеченных view-transition-name, до и после перехода. Эти снимки отображаются в дереве псевдоэлементов. По умолчанию создается "плоское" дерево. Это означает, что исходная иерархия в DOM теряется, а все захваченные группы переходов представления являются дочерними элементами одного псевдоэлемента ::view-transition.

Такой подход подходит для многих случаев, но не для всех. Ниже приведены примеры эффектов, которые могут иметь неожиданный визуальный эффект в плоском дереве:

  • Обрезка (overflow, clip-path, border-radius): обрезка влияет на дочерние элементы, а это значит, что элементы одного уровня в группе перехода не могут обрезать друг друга.
  • opacity, mask-image и filter. Эти эффекты также предназначены для работы с полностью растрированным изображением дерева, влияя на дочерние элементы, а не на каждый элемент по отдельности.
  • 3D-преобразования (transform-style, transform, perspective). Чтобы отображались все анимации 3D-преобразований, необходимо соблюдать определенную иерархию.

В примере ниже показано плоское псевдодерево с элементами, которые обрезаются предком в дереве DOM. Эти элементы теряют обрезку во время перехода между представлениями, что приводит к нарушению визуального эффекта.

Запись неправильного эффекта обрезки во время перехода между представлениями. Текст должен быть обрезан диалоговым окном, но этого не происходит. Анимация замедлена, чтобы подчеркнуть эффект.

Вложенные группы переходов между представлениями – это расширение для переходов между представлениями, которое позволяет вкладывать псевдоэлементы ::view-transition-group друг в друга. Если группы переходов вложены, можно восстановить такие эффекты, как обрезка во время перехода.

Browser Support

  • Chrome: 140.
  • Edge: 140.
  • 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().

Чтобы изменить родительскую группу, вы можете:

  • В родительском элементе задайте для свойства 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) обрезал абзацы, примените overflow: clip к псевдоэлементу ::view-transition-group-children(card):

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

Результат будет следующим:

Демонстрация

Демозапись

Демозапись (замедленная)

Псевдоэлемент ::view-transition-group-children присутствует, только если используются вложенные группы. Он имеет размер поля границы исходного элемента и прозрачную границу той же формы и толщины, что и элемент, создавший псевдоэлемент – card в предыдущем примере.

Клипы и не только

Вложенные группы переходов между представлениями используются не только для эффектов обрезки. Другой пример – 3D-эффекты. В демонстрации ниже есть возможность повернуть карточку в 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;
    }
}

Без вложенных групп переходов аватара и имя не вращаются вместе с карточкой.

Демонстрация

Демозапись

Демозапись (замедленная)

By nesting the avatar and name pseudos inside the card, the 3D effect can be restored. Но это ещё не всё. Помимо псевдоэлементов ::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).

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

Recap

Вложенные переходы между представлениями позволяют сохранить часть топологии дерева DOM при создании псевдоэлементов. Это позволяет использовать различные эффекты, которые ранее были недоступны при переходах между представлениями. Некоторые из них описаны в этой статье.

Вложенность меняет модель построения переходов между представлениями и предназначена для создания сложных эффектов. Как отмечалось, переходы между представлениями с областью действия элемента также могут выполнять подмножество эффектов с более простой моделью. Рекомендуем попробовать обе функции и выбрать ту, которая лучше всего подходит для ваших целей.