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

Опубликовано: 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: не поддерживается.
  • Safari: не поддерживается.

От плоского псевдодерева к вложенному псевдодереву

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

Живая демонстрация

Демо-запись

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

Если внимательно посмотреть на демонстрацию, можно заметить проблему с переходом: несмотря на то, что абзацы с описанием являются дочерними элементами элемента <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-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 присутствует только при использовании вложенных групп. Его размер соответствует размеру border-box исходного элемента, и ему присваивается прозрачная рамка той же формы и толщины, что и элемент, сгенерировавший псевдоэлемент — 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;
    }
}

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

Живая демонстрация

Демо-запись

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

Вложив псевдонимы аватара и имени внутрь карточки, можно восстановить 3D-эффект. Но это не единственное, что нужно сделать. Помимо поворота псевдонимов ::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;
}

Краткий обзор

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

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