Evite problemas de corte (e muito mais) nas transições de visualização usando grupos aninhados de transição de visualização

Publicado em: 22 de setembro de 2025

Quando você inicia uma transição de visualização, o navegador faz capturas de tela automáticas 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 estilo que não podem ser alcançados com ela. Confira a seguir exemplos de efeitos que podem ter um efeito visual inesperado em uma árvore simples:

  • Recorte (overflow, clip-path, border-radius): o recorte afeta os filhos do elemento, o que significa que os irmãos do grupo de transição de visualização não podem se recortar.
  • opacity, mask-image e filter: da mesma forma, esses efeitos são projetados para funcionar em uma imagem totalmente rasterizada de uma árvore, afetando os filhos, em vez de cada item individualmente.
  • Transformações 3D (transform-style, transform, perspective): para mostrar toda a gama de animações de transformação 3D, é necessário manter uma hierarquia.

O exemplo a seguir mostra uma pseudoárvore simples, com elementos que são cortados por um ancestral na árvore do DOM. Esses elementos perdem o recorte durante a transição de visualização, resultando em um efeito visual quebrado.

Gravação de um efeito de corte quebrado durante uma transição de visualização. O texto deveria ser cortado pela caixa de diálogo, mas não é. O tempo de animação é reduzido para exagerar o efeito.

Os grupos de transição de visualização aninhados são uma extensão das transições de visualização que permitem aninhar pseudoelementos ::view-transition-group uns nos outros. Quando os grupos de transição de visualização estão aninhados, é possível restaurar efeitos como o corte durante a transição.

Browser Support

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

De uma pseudoflat para uma pseudoárvore aninhada

Na demonstração a seguir, você pode clicar 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 a demonstração com atençã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 maneira como as transições de visualização criam e renderizam a pseudoárvore:

  • Na pseudoárvore, por padrão, todos os snapshots são irmãos uns dos outros.
  • A pseudoárvore é renderizada em um pseudoelemento ::view-transition, que é renderizado sobre todo o documento.

Para esta demonstração, especificamente, a árvore do DOM é assim:

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 pintados sobre o card.

Para que ::view-transition-group(card) corte ::view-transition-group(.text), os pseudoelementos ::view-transition-group(.text) precisam ser filhos de ::view-transition-group(card). Para isso, use view-transition-group, que permite atribuir um "grupo pai" a um pseudoelemento ::view-transition-group() gerado.

Para mudar o grupo pai, você tem duas opções:

  • No pai, defina view-transition-group como contain para que ele contenha todos os filhos com um view-transition-name.
  • Em todos os filhos, defina o view-transition-group como o view-transition-name do pai. Também é possível usar nearest para segmentar o grupo ancestral mais próximo.

Para usar grupos de transição de visualização aninhados nesta demonstração, 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 são aninhados no pseudoelemento ::view-transition-group(card). Isso é feito em um pseudo ::view-transition-group-children(…) extra, que mantém todos os pseudos aninhados juntos:

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

English: Finally, to have the ::view-transition-group(card) pseudo clip the paragraphs, apply overflow: clip onto the ::view-transition-group-children(card) pseudo:

::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 caixa de borda 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 de transições de visualização aninhados 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 isso não é tudo. Além de girar os pseudoelementos ::view-transition-old(card) e ::view-transition-new(card), você também precisa girar o ::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, os grupos de transição de visualização aninhados são usados para garantir que os cards sejam cortados pelo scroller ancestral. É possível ativar ou desativar o uso de grupos de transição de visualização aninhados usando os controles incluídos.

Demonstração ao vivo

Gravação da demonstração

O interessante dessa demonstração é que todos os pseudos ::view-transition-group(.card) são aninhados e cortados pelo pseudo ancestral ::view-transition-group(cards). 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 do DOM ao criar os pseudoelementos. Isso possibilita uma variedade de efeitos que antes não eram possíveis 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 criadas e é usado para criar efeitos avançados. Como observado, as transições de visualização com escopo de 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.