Publié le 22 septembre 2025
Lorsque vous démarrez une transition de vue, le navigateur prend automatiquement des instantanés avant et après des éléments tagués avec un view-transition-name
. Ces instantanés sont affichés dans une arborescence de pseudo-éléments. Par défaut, l'arborescence générée est "plate". Cela signifie que la hiérarchie d'origine dans le DOM est perdue et que tous les groupes de transition de vue capturés sont des frères et sœurs sous un seul pseudo-élément ::view-transition
.
Cette approche d'arborescence à plat est suffisante pour de nombreux cas d'utilisation, mais certains cas d'utilisation de style ne peuvent pas être réalisés avec elle. Voici quelques exemples d'effets pouvant avoir un impact visuel inattendu dans un arbre plat :
- Découpage (
overflow
,clip-path
,border-radius
) : le découpage affecte les enfants de l'élément, ce qui signifie que les éléments frères du groupe de transition de vue ne peuvent pas se découper mutuellement. opacity
,mask-image
etfilter
: de même, ces effets sont conçus pour fonctionner sur une image entièrement rasterisée d'un arbre, en affectant les enfants plutôt que chaque élément individuellement.- Transformations 3D (
transform-style
,transform
,perspective
) : pour afficher toute la gamme d'animations de transformation 3D, une certaine hiérarchie doit être conservée.
L'exemple suivant montre un pseudo-arbre plat, avec des éléments qui sont coupés par un ancêtre dans l'arborescence DOM. Ces éléments perdent leur écrêtage lors de la transition de vue, ce qui entraîne un effet visuel cassé.
Les groupes de transitions de vues imbriqués sont une extension des transitions de vues qui vous permet d'imbriquer des pseudo-éléments ::view-transition-group
les uns dans les autres. Lorsque les groupes de transition de vue sont imbriqués, il est possible de restaurer des effets tels que le clipping pendant la transition.
Browser Support
D'un pseudo-arbre plat à un pseudo-arbre imbriqué
Dans la démonstration suivante, vous pouvez cliquer sur l'avatar d'une personne pour afficher plus d'informations à son sujet. Les animations sont gérées par une transition de vue dans le même document, qui transforme le bouton sur lequel l'utilisateur a cliqué en boîte de dialogue, déplace l'avatar et le nom sur l'écran, et fait glisser les paragraphes de la boîte de dialogue vers le haut ou vers le bas.
Démo
Enregistrement de la démonstration
Enregistrement de la démonstration (ralenti)
Si vous examinez attentivement la démo, vous remarquerez un problème de transition : même si les paragraphes contenant la description sont des enfants de l'élément <dialog>
dans le DOM, le texte n'est pas coupé par la boîte <dialog>
lors de la transition :
<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>
L'application de overflow: clip
sur <dialog>
ne fait rien non plus.
Le problème réside dans la façon dont les transitions d'affichage créent et affichent leur pseudo-arborescence :
- Dans le pseudo-arbre, par défaut, tous les instantanés sont frères et sœurs.
- Le pseudo-arbre est affiché dans un pseudo-élément
::view-transition
qui s'affiche au-dessus de l'ensemble du document.
Pour cette démo en particulier, l'arborescence DOM se présente comme suit :
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
└─ …
Comme les pseudos ::view-transition-group(.text)
sont des frères et sœurs successifs du pseudo ::view-transition-group(card)
, ils sont peints au-dessus de la carte.
Pour que l'extrait ::view-transition-group(card)
::view-transition-group(.text)
fonctionne, les pseudos ::view-transition-group(.text)
doivent être des enfants de ::view-transition-group(card)
. Pour ce faire, utilisez view-transition-group
, qui vous permet d'attribuer un "groupe parent" à un pseudo-élément ::view-transition-group()
généré.
Pour modifier le groupe parent, vous avez deux options :
- Sur le parent, définissez
view-transition-group
surcontain
pour qu'il contienne tous les enfants avec unview-transition-name
. - Sur tous les enfants, définissez
view-transition-group
sur leview-transition-name
du parent. Vous pouvez également utilisernearest
pour cibler le groupe ancêtre le plus proche.
Pour cette démo, le code devient donc :
button.clicked,
dialog {
view-transition-group: contain;
}
Ou
button.clicked,
dialog *,
view-transition-group: nearest;
}
Avec ce code en place, les pseudos ::view-transition-group(.text)
sont désormais imbriqués dans le pseudo ::view-transition-group(card)
. Pour ce faire, un pseudo-élément ::view-transition-group-children(…)
supplémentaire est utilisé, ce qui permet de regrouper tous les pseudo-éléments imbriqués :
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
└─ …
Enfin, pour que le pseudo ::view-transition-group(card)
écrête les paragraphes, appliquez overflow: clip
au pseudo ::view-transition-group-children(card)
:
::view-transition-group-children(card) {
overflow: clip;
}
Le résultat est le suivant :
Démo
Enregistrement de la démonstration
Enregistrement de la démonstration (ralenti)
Le pseudo-élément ::view-transition-group-children
n'est présent que lorsque des groupes imbriqués sont utilisés. Il est dimensionné à la border-box de l'élément d'origine et reçoit une bordure transparente de même forme et épaisseur que l'élément qui a généré le pseudo-élément, à savoir card
dans l'exemple précédent.
Extraits et plus
Les groupes de transitions de vue imbriqués sont utilisés ailleurs que dans les effets de découpage. Les effets 3D en sont un autre exemple. Dans la démo suivante, vous pouvez faire pivoter la carte en 3D pendant la transition.
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;
}
}
Sans groupes de transition de vue imbriqués, l'avatar et le nom ne pivotent pas avec la carte.
Démo
Enregistrement de la démonstration
Enregistrement de la démonstration (ralenti)
En imbriquant les pseudos de l'avatar et du nom dans la carte, l'effet 3D peut être restauré. Mais ce n'est pas tout. En plus de faire pivoter les pseudos ::view-transition-old(card)
et ::view-transition-new(card)
, vous devez également faire pivoter celui de ::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;
}
}
Démo
Enregistrement de la démonstration
Enregistrement de la démonstration (ralenti)
Plus de démos
Dans l'exemple suivant, des groupes de transitions de vue imbriqués sont utilisés pour s'assurer que les cartes sont coupées par leur élément de défilement parent. Vous pouvez activer ou désactiver l'utilisation de groupes de transition de vue imbriqués à l'aide des commandes incluses.
Démo
Enregistrement de la démonstration
L'intérêt de cette démo est que tous les pseudos ::view-transition-group(.card)
sont imbriqués dans le pseudo ::view-transition-group(cards)
ancêtre et sont coupés par celui-ci. L'élément #targeted-card
est exclu, car son animation d'entrée/sortie ne doit pas être coupée par ::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;
}
Récapitulatif
Les transitions de vue imbriquées vous permettent de conserver une partie de la topologie de l'arborescence DOM lors de la construction des pseudo-éléments. Cela permet de créer des effets qui n'étaient pas possibles auparavant avec les transitions de vue, dont certains que nous avons décrits ici.
L'imbrication modifie le modèle de construction des transitions de vue et est destinée à créer des effets avancés. Comme indiqué, les transitions de vue à portée d'élément peuvent également produire un sous-ensemble d'effets avec un modèle plus simple. Nous vous encourageons à essayer les deux fonctionnalités pour déterminer celle qui répond le mieux à vos besoins.