Publicado em: 22 de setembro de 2025
Ao iniciar uma transição de visualização, o navegador faz automaticamente snapshots de antes e depois dos elementos marcados com um view-transition-name
. Esses snapshots são renderizados em uma árvore de pseudoelementos. Por padrão, a árvore gerada é "plana". Isso significa que a hierarquia original no DOM é perdida, e todos os grupos de transição de visualização capturados são irmãos em um único pseudoelemento ::view-transition
.
Essa abordagem de árvore simples é suficiente para muitos casos de uso, mas há alguns casos de uso de estilização que não podem ser alcançados com ela. Confira alguns exemplos de efeitos que podem ter um efeito visual inesperado em uma árvore plana:
- Corte (
overflow
,clip-path
,border-radius
): o corte afeta os filhos do elemento, o que significa que os irmãos do grupo de transição de visualização não podem cortar uns aos outros. opacity
,mask-image
efilter
: da mesma forma, esses efeitos são projetados para funcionar em uma imagem totalmente rasterizada de uma árvore, afetando os filhos, em vez de afetar cada item individualmente.- Transformações 3D (
transform-style
,transform
,perspective
): para mostrar toda a variedade de animações de transformação 3D, é necessário manter alguma hierarquia.
O exemplo a seguir mostra uma pseudárvore plana, com elementos cortados por um ancestral na árvore DOM. Esses elementos perdem o corte durante a transição de visualização, resultando em um efeito visual quebrado.
Grupos de transição de visualização aninhados é uma extensão das transições de visualização que permite aninhar pseudoelementos ::view-transition-group
uns nos outros. Quando os grupos de transição de visualização são aninhados, é possível restaurar efeitos como corte durante a transição.
Browser Support
De uma pseudoárvore plana para uma aninhada
Na demonstração a seguir, clique no avatar de uma pessoa para ver mais informações sobre ela. As animações são processadas por uma transição de visualização no mesmo documento que transforma o botão clicado na caixa de diálogo, move o avatar e o nome pela tela e desliza os parágrafos da caixa de diálogo para cima ou para baixo.
Demonstração ao vivo
Gravação da demonstração
Gravação da demonstração (mais lenta)
Se você observar atentamente a demonstração, vai notar que há um problema com a transição: embora os parágrafos com a descrição sejam filhos do elemento <dialog>
no DOM, o texto não é cortado pela caixa do <dialog>
durante a transição:
<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>
Aplicar overflow: clip
no <dialog>
também não faz nada.
O problema é a forma como as transições de visualização criam e renderizam a pseudárvore:
- Na pseudárvore, por padrão, todos os snapshots são irmãos uns dos outros.
- A pseudárvore é renderizada em um pseudoelemento
::view-transition
, que é renderizado na parte superior de todo o documento.
Para esta demonstração específica, a árvore DOM tem esta aparência:
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
└─ …
Como os pseudoelementos ::view-transition-group(.text)
são irmãos sucessores do pseudoelemento ::view-transition-group(card)
, eles são renderizados em cima do card.
Para ter um clipe ::view-transition-group(.text)
de ::view-transition-group(card)
, os pseudoelementos ::view-transition-group(.text)
precisam ser filhos do ::view-transition-group(card)
. Para isso, use view-transition-group
, que permite atribuir um "grupo principal" a um pseudoelemento ::view-transition-group()
gerado.
Para mudar o grupo principal, você tem duas opções:
- No elemento pai, defina o
view-transition-group
comocontain
para que ele contenha todos os filhos com umview-transition-name
. - Em todos os filhos, defina o
view-transition-group
como oview-transition-name
do pai. Também é possível usarnearest
para segmentar o grupo ancestral mais próximo.
Portanto, para esta demonstração, para usar grupos de transição de visualização aninhados, o código fica assim:
button.clicked,
dialog {
view-transition-group: contain;
}
Ou
button.clicked,
dialog *,
view-transition-group: nearest;
}
Com esse código, os pseudoelementos ::view-transition-group(.text)
agora ficam aninhados dentro do pseudoelemento ::view-transition-group(card)
. Isso é feito em um pseudoelemento ::view-transition-group-children(…)
extra, que mantém todos os pseudoelementos aninhados juntos:
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
└─ …
Por fim, para que o pseudo ::view-transition-group(card)
corte os parágrafos, aplique overflow: clip
no pseudo ::view-transition-group-children(card)
:
::view-transition-group-children(card) {
overflow: clip;
}
O resultado é o seguinte:
Demonstração ao vivo
Gravação da demonstração
Gravação da demonstração (mais lenta)
O pseudo ::view-transition-group-children
só está presente quando grupos aninhados são usados. Ele é dimensionado para a border-box do elemento original e recebe uma borda transparente com o mesmo formato e espessura do elemento que gerou o pseudoelemento, card
no exemplo anterior.
Corte e muito mais
Os grupos aninhados de transições de visualização são usados em outros lugares além dos efeitos de corte. Outro exemplo são os efeitos 3D. Na demonstração a seguir, há uma opção para girar o card em 3D durante a transição.
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;
}
}
Sem grupos de transição de visualização aninhados, o avatar e o nome não giram com o card.
Demonstração ao vivo
Gravação da demonstração
Gravação da demonstração (mais lenta)
Ao aninhar os pseudoelementos de avatar e nome dentro do card, o efeito 3D pode ser restaurado. Mas não é só isso. Além de fazer a rotação dos pseudoelementos ::view-transition-old(card)
e ::view-transition-new(card)
, você também precisa fazer a rotação do ::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;
}
}
Demonstração ao vivo
Gravação da demonstração
Gravação da demonstração (mais lenta)
Mais demonstrações
No exemplo a seguir, grupos de transição de visualização aninhados são usados para garantir que os cards sejam cortados pelo rolador ancestral. É possível ativar ou desativar o uso de grupos aninhados de transição de visualização usando os controles incluídos.
Demonstração ao vivo
Gravação da demonstração
O interessante dessa demonstração é que todos os pseudoelementos ::view-transition-group(.card)
ficam aninhados dentro do pseudoelemento ancestral ::view-transition-group(cards)
e são cortados por ele. O #targeted-card
é excluído porque a animação de entrada/saída não pode ser cortada pelo ::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;
}
Recapitulação
As transições de visualização aninhadas permitem preservar parte da topologia da árvore DOM ao construir os pseudoelementos. Isso desbloqueia uma variedade de efeitos que não eram possíveis antes com as transições de visualização, alguns dos quais descrevemos aqui.
O aninhamento muda o modelo de como as transições de visualização são construídas e deve ser usado para criar efeitos avançados. Como observado, as transições de visualização no escopo do elemento também podem realizar um subconjunto dos efeitos com um modelo mais simples. Recomendamos que você teste os dois recursos para decidir qual deles atende melhor às suas necessidades.