Evita problemas de recorte (y mucho más) en las transiciones de vista con grupos de transición de vista anidados

Publicado: 22 de septiembre de 2025

Cuando inicias una transición de vista, el navegador toma automáticamente instantáneas antes y después de los elementos etiquetados con un view-transition-name. Estas instantáneas se renderizan en un árbol de seudoelementos. De forma predeterminada, el árbol generado es "plano". Esto significa que se pierde la jerarquía original en el DOM y todos los grupos de transiciones de vistas capturados son hermanos en un solo pseudoelemento ::view-transition.

Este enfoque de árbol plano es suficiente para muchos casos de uso, pero hay algunos casos de uso de diseño que no se pueden lograr con él. Los siguientes son ejemplos de efectos que pueden tener un efecto visual inesperado en un árbol plano:

  • Recorte (overflow, clip-path, border-radius): El recorte afecta a los elementos secundarios del elemento, lo que significa que los hermanos del grupo de transición de vistas no pueden recortarse entre sí.
  • opacity, mask-image y filter: De manera similar, estos efectos están diseñados para funcionar en una imagen completamente rasterizada de un árbol, lo que afecta a los elementos secundarios, en lugar de afectar a cada elemento de forma individual.
  • Transformaciones 3D (transform-style, transform, perspective): Para mostrar el rango completo de animaciones de transformación 3D, se debe mantener cierta jerarquía.

En el siguiente ejemplo, se muestra un pseudoárbol plano, con elementos que un ancestro recorta en el árbol del DOM. Estos elementos pierden su recorte durante la transición de la vista, lo que genera un efecto visual roto.

Grabación de un efecto de recorte roto mientras se ejecuta una transición de vista. El diálogo debería recortar el texto, pero no lo hace. El tiempo de animación se ralentiza para exagerar el efecto.

Los grupos de transiciones de vistas anidadas son una extensión de las transiciones de vistas que te permite anidar pseudoelementos ::view-transition-group entre sí. Cuando se anidan los grupos de transición de vistas, es posible restablecer efectos como el recorte durante la transición.

Browser Support

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

De un pseudoárbol plano a un pseudoárbol anidado

En la siguiente demostración, puedes hacer clic en el avatar de una persona para ver más información sobre ella. Las animaciones se controlan con una transición de vistas del mismo documento que transforma el botón en el que se hizo clic en el diálogo, mueve el avatar y el nombre por la pantalla, y desliza los párrafos del diálogo hacia arriba o hacia abajo.

Demostración en directo

Grabación de la demostración:

Grabación de la demostración (tempo lento)

Si observas detenidamente la demostración, verás que hay un problema con la transición: aunque los párrafos con la descripción son elementos secundarios del elemento <dialog> en el DOM, el texto no se recorta con el cuadro de <dialog> durante la transición:

<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 en <dialog> tampoco hace nada.

El problema es la forma en que las transiciones de vistas compilan y renderizan su pseudoárbol:

  • En el pseudoárbol, de forma predeterminada, todas las instantáneas son hermanas entre sí.
  • El pseudoárbol se renderiza en un pseudoelemento ::view-transition que se renderiza sobre todo el documento.

Específicamente para esta demostración, el árbol del DOM se ve de la siguiente manera:

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 los seudoelementos ::view-transition-group(.text) son hermanos posteriores del seudoelemento ::view-transition-group(card), se pintan sobre la tarjeta.

Para que ::view-transition-group(card) recorte ::view-transition-group(.text), los seudoelementos ::view-transition-group(.text) deben ser elementos secundarios de ::view-transition-group(card). Para ello, usa view-transition-group, que te permite asignar un "grupo superior" para un pseudoelemento ::view-transition-group() generado.

Para cambiar el grupo superior, tienes dos opciones:

  • En el elemento superior, establece view-transition-group en contain para que contenga todos los elementos secundarios con un view-transition-name.
  • En todos los elementos secundarios, establece el view-transition-group en el view-transition-name del elemento superior. También puedes usar nearest para segmentar el grupo de principales más cercano.

Por lo tanto, para esta demostración, si quieres usar grupos de transiciones de vistas anidadas, el código se convierte en lo siguiente:

button.clicked,
dialog {
  view-transition-group: contain;
}

O

button.clicked,
dialog *,
  view-transition-group: nearest;
}

Con este código, los pseudoelementos ::view-transition-group(.text) ahora se anidan dentro del pseudoelemento ::view-transition-group(card). Esto se hace en un seudoelemento ::view-transition-group-children(…) adicional, que mantiene juntos todos los seudoelementos anidados:

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

Por último, para que el seudoelemento ::view-transition-group(card) recorte los párrafos, aplica overflow: clip al seudoelemento ::view-transition-group-children(card):

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

El resultado es el siguiente:

Demostración en directo

Grabación de la demostración:

Grabación de la demostración (tempo lento)

El seudónimo ::view-transition-group-children solo está presente cuando se usan grupos anidados. Tiene el tamaño de la caja de borde del elemento original y se le asigna un borde transparente con la misma forma y grosor de borde que el elemento que generó el pseudoelemento, card en el ejemplo anterior.

Recortes y más

Los grupos de transiciones de vistas anidadas se usan en lugares que no son efectos de recorte. Otro ejemplo son los efectos 3D. En la siguiente demostración, hay una opción para rotar la tarjeta en 3D durante la transición.

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

Sin grupos de transiciones de vistas anidados, el avatar y el nombre no rotan junto con la tarjeta.

Demostración en directo

Grabación de la demostración:

Grabación de la demostración (tempo lento)

Si anidas los seudoelementos de avatar y nombre dentro de la tarjeta, se puede restablecer el efecto 3D. Pero eso no es lo único que debes hacer. Además de rotar los seudoelementos ::view-transition-old(card) y ::view-transition-new(card), también debes rotar el seudoelemento ::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;
    }
}

Demostración en directo

Grabación de la demostración:

Grabación de la demostración (tempo lento)

Más demostraciones

En el siguiente ejemplo, se usan grupos de transiciones de vistas anidadas para garantizar que el elemento de desplazamiento superior recorte las tarjetas. Puedes activar o desactivar el uso de grupos de transiciones de vistas anidadas con los controles incluidos.

Demostración en directo

Grabación de la demostración:

Lo interesante de esta demostración es que todos los seudoelementos ::view-transition-group(.card) se anidan dentro del seudoelemento principal ::view-transition-group(cards) y se recortan por él. Se excluye el #targeted-card porque su animación de entrada/salida no debe recortarse por el ::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;
}

Resumen

Las transiciones de vistas anidadas te permiten conservar parte de la topología del árbol del DOM cuando construyes los seudoelementos. Esto habilita una variedad de efectos que antes no eran posibles con las transiciones de vistas, algunos de los cuales describimos aquí.

El anidamiento cambia el modelo de cómo se construyen las transiciones de vistas y está pensado para crear efectos avanzados. Como se indicó, las transiciones de vistas con alcance de elemento también pueden lograr un subconjunto de los efectos con un modelo más simple. Te recomendamos que pruebes ambas funciones para decidir cuál se adapta mejor a tus necesidades.