Animar a height: auto; (y otras palabras clave de tamaño intrínseco) en CSS

Usa la propiedad interpolate-size o la función calc-size() para permitir transiciones y animaciones fluidas de longitudes a palabras clave de tamaño intrínseco y viceversa.

Fecha de publicación: 17 de septiembre de 2024

Introducción

Una función de CSS que se solicita con frecuencia es la capacidad de animar a height: auto. Una leve variación de esa solicitud es hacer la transición de la propiedad width en lugar de height o de cualquiera de los otros tamaños intrínsecos representados por palabras clave como min-content, max-content y fit-content.

Por ejemplo, en la siguiente demostración, sería bueno que las etiquetas se animaran suavemente a su ancho natural cuando se coloca el cursor sobre los íconos.

El CSS que se usa es el siguiente:

nav a {
    width: 80px;
    overflow-x: clip;
    transition: width 0.35s ease; /* 👈 Transition the width */

    &:hover,
    &:focus-visible {
        width: max-content; /* 👈 Doesn't work with transitions */
    }
}

Aunque se declara un transition para migrar la propiedad width y se declara width: auto en :hover, no se produce una transición fluida. En cambio, el cambio es abrupto.

Anima de y hacia palabras clave de tamaño intrínseco con interpolate-size

Navegadores compatibles

  • Chrome: 129
  • Edge: No es compatible.
  • Firefox: No es compatible.
  • Safari: no es compatible.

Origen

La propiedad interpolate-size de CSS te permite controlar si se deben permitir o no las animaciones y transiciones de los keywords de tamaño intrínseco de CSS.

Su valor predeterminado es numeric-only, que no permite la interpolación. Cuando configuras la propiedad en allow-keywords, aceptas interpolar las longitudes de las palabras clave de tamaño intrínseco de CSS en los casos en que el navegador pueda animar esas palabras clave.

Según las especificaciones:

  • numeric-only: No se puede interpolar un <intrinsic-size-keyword>.
  • allow-keywords: Se pueden interpolar dos valores si uno de ellos es un <intrinsic-size-keyword> y el otro es un <length-percentage>. […]

Dado que la propiedad interpolate-size es una que se hereda, puedes declararla en :root para habilitar la transición hacia y desde palabras clave de tamaño intrínseco en todo el documento. Este es el enfoque recomendado.

/* Opt-in the whole page to interpolate sizes to/from keywords */
:root {
    interpolate-size: allow-keywords; /* 👈 */
}

En la siguiente demostración, esta regla se agrega al código. Como resultado, las animaciones hacia y desde width: auto funcionan bien (en navegadores compatibles):

Limita el alcance de la habilitación reduciendo el selector

Si deseas limitar la habilitación de allow-keywords a solo un subárbol de tu documento, ajusta el selector de :root solo al elemento al que deseas segmentar. Por ejemplo, en caso de que el <header> de tu página no sea compatible con este tipo de transiciones, puedes limitar la habilitación solo al elemento <main> y sus elementos secundarios de la siguiente manera:

main { /* 👈 Scope the opt-in to only <main> and its descendants */
    interpolate-size: allow-keywords;
}

¿Por qué no se permite la animación de las palabras clave de tamaño de forma predeterminada?

Una opinión común sobre este mecanismo de habilitación es que los navegadores solo deberían permitir transiciones y animaciones de palabras clave de tamaño intrínseco a longitudes de forma predeterminada.

La opción para habilitar este comportamiento se investigó durante el desarrollo de la función. El grupo de trabajo descubrió que habilitar esta opción de forma predeterminada no es retrocompatible porque muchas hojas de estilo suponen que las palabras clave de tamaño intrínseco (como auto o min-content) no se pueden animar. Puedes encontrar los detalles en este comentario sobre el problema relevante del grupo de trabajo del CSS.

Por lo tanto, la propiedad se debe habilitar. Gracias a su característica de herencia, habilitar un documento completo es solo una declaración interpolate-size: allow-sizes en :root, como se detalló anteriormente.

Cómo animar hacia y desde palabras clave de tamaño intrínseco con calc-size()

Navegadores compatibles

  • Chrome: 129.
  • Edge: 129.
  • Firefox: No es compatible.
  • Safari: No se admite.

Origen

Otra forma de habilitar la interpolación hacia y desde palabras clave de tamaño intrínseco es usar la función calc-size(). Permite aplicar las matemáticas en tamaños intrínsecos de una manera segura y bien definida.

La función acepta dos argumentos, en orden:

  • Una base de tamaño de cálculo, que puede ser un <intrinsic-size-keyword>, pero también un calc-size() anidado.
  • Un cálculo de tamaño de cálculo, que te permite realizar cálculos con la base de tamaño de cálculo. Para hacer referencia a la base de tamaño de cálculo, usa la palabra clave size.

Estos son algunos ejemplos:

width: calc-size(auto, size);        // = the auto width, unaltered
width: calc-size(min-content, size); // = the min-content width, unaltered

Si agregas calc-size() a la demostración original, el código se verá de la siguiente manera:

nav a {
    width: 80px;
    overflow-x: clip;
    transition: width 0.35s ease;

    &:hover,
    &:focus-visible {
        width: calc-size(max-content, size); /* 👈 */
    }
}

Visualmente, el resultado es exactamente el mismo que cuando se usa interpolate-size. Por lo tanto, en este caso específico, debes usar interpolate-size.

Donde calc-size() brilla es en su capacidad para realizar cálculos, algo que no se puede hacer con interpolate-size:

width: calc-size(auto, size - 10px); // = The auto width minus 10 pixels
width: calc-size(min-content, size + 1rem); // = The min-content width plus 1rem
width: calc-size(max-content, size * .5);   // = Half the max-content width

Por ejemplo, si deseas que todos los párrafos de una página tengan el tamaño del múltiplo más cercano de 50px, puedes usar lo siguiente:

p {
    width: calc-size(fit-content, round(up, size, 50px));
    height: calc-size(auto, round(up, size, 50px));
}

calc-size() también te permite interpolar entre dos calc-size() cuando ambas bases de tamaño de cálculo son idénticas. Esto también es algo que no se puede lograr con interpolate-size.

#element {
    width: min-content; /* 👈 */
    transition: width 0.35s ease;

    &:hover {
        width: calc-size(min-content, size + 10px); /* 👈 */
    }
}

¿Por qué no se permite <intrinsic-size-keyword> en calc()?

Una pregunta que suele surgir con calc-size() es por qué el grupo de trabajo del CSS no ajustó la función calc() para admitir palabras clave de tamaño intrínseco.

Uno de los motivos es que no puedes combinar palabras clave de tamaño intrínseco cuando realizas cálculos. Por ejemplo, podrías tener la tentación de escribir calc(max-content - min-content), que parece válido, pero en realidad no lo es. calc-size() aplica la corrección porque, a diferencia de calc(), solo acepta un solo <intrinsic-size-keyword> como primer argumento.

Otro motivo es la conciencia contextual. Algunos algoritmos de diseño tienen un comportamiento especial para palabras clave de tamaño intrínseco específicas. calc-size() se define de forma explícita para representar un tamaño intrínseco, no un <length>. Gracias a esto, esos algoritmos pueden tratar calc-size(<intrinsic-size-keyword>, …) como <intrinsic-size-keyword> y mantener su comportamiento especial para esa palabra clave.

¿Qué enfoque usar?

En la mayoría de los casos, declara interpolate-size: allow-keywords en :root. Es la forma más fácil de habilitar la animación hacia y desde palabras clave de ajuste de tamaño intrínseco, ya que es, en esencia, una sola línea.

/* Opt-in the whole page to animating to/from intrinsic sizing keywords */
:root {
    interpolate-size: allow-keywords; /* 👈 */
}

Este fragmento de código es una buena mejora progresiva, ya que los navegadores que no lo admiten recurrirán a no usar transiciones.

Cuando necesites un control más detallado sobre algunos aspectos, como realizar cálculos, o quieras usar un comportamiento que solo calc-size() puede hacer, puedes recurrir a calc-size().

#specific-element {
    width: 50px;

    &:hover {
        width: calc-size(fit-content, size + 1em); /* 👈 Only calc-size() can do this */
    }
}

Sin embargo, usar calc-size() en tu código requerirá que incluyas resguardos para los navegadores que no admiten calc-size(). Por ejemplo, agregar declaraciones de tamaño adicionales o recurrir a la detección de componentes con @supports.

width: fit-content;
width: calc-size(fit-content, size + 1em);
       /* 👆 Browsers with no calc-size() support will ignore this second declaration,
             and therefore fall back to the one on the line before it. */

Más demostraciones

Estos son algunos ejemplos más que usan interpolate-size: allow-keywords a su favor.

Notificaciones

La siguiente demostración es una bifurcación de esta demo de @starting-style. Se ajustó el código para permitir que se agreguen elementos de distintas alturas.

Para lograrlo, toda la página habilita la interpolación de palabras clave de tamaño y el height de cada elemento .item se establece en auto. De lo contrario, el código es exactamente el mismo que el anterior al forking.

:root {
    interpolate-size: allow-keywords; /* 👈 */
}

.item {
    height: auto; /* 👈 */

    @starting-style {
        height: 0px;
    }
}

Cómo animar el elemento <details>

Un caso de uso típico en el que te gustaría usar este tipo de interpolación es animar un widget de divulgación o un acordeón exclusivo a medida que se abre. En HTML, se usa el elemento <details> para esto.

Con interpolate-size: allow-keywords, puedes llegar bastante lejos:

@supports (interpolate-size: allow-keywords) {
    :root {
        interpolate-size: allow-keywords;
    }
    
    details {
        transition: height 0.5s ease;
        height: 2.5rem;
        
        &[open] {
            height: auto;
            overflow: clip; /* Clip off contents while animating */
        }
    }
}

Sin embargo, como puedes ver, la animación solo se ejecuta cuando se abre el widget de divulgación. Para ello, Chrome está trabajando en el pseudo ::details-content, que se lanzará en Chrome más adelante este año (y del que hablaremos en una próxima publicación). Si combinas interpolate-size: allow-keywords y ::details-content, puedes obtener una animación en ambas direcciones: