Animar para altura: automático; (e outras palavras-chave de dimensionamento intrínseco) no CSS

Use a propriedade interpolate-size ou a função calc-size() para permitir transições e animações suaves de comprimentos a palavras-chave de dimensionamento intrínseco e volta.

Publicado em 17 de setembro de 2024

Introdução

Um recurso do CSS solicitado com frequência é a capacidade de animar para height: auto. Uma pequena variação dessa solicitação é fazer a transição da propriedade width em vez de height ou para qualquer um dos outros tamanhos intrínsecos representados por palavras-chave como min-content, max-content e fit-content.

Por exemplo, na demonstração a seguir, seria bom se os rótulos fossem animados suavemente para a largura natural ao passar o cursor sobre os ícones.

O CSS usado é o seguinte:

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 */
    }
}

Mesmo que um transition seja declarado para fazer a transição da propriedade width e width: auto seja declarado em :hover, nenhuma transição suave acontece. Em vez disso, a mudança é abrupta.

Animar para e de palavras-chave de dimensionamento intrínseco com interpolate-size

Compatibilidade com navegadores

  • Chrome: 129.
  • Edge: não compatível.
  • Firefox: não é compatível.
  • Safari: incompatível.

Origem

A propriedade CSS interpolate-size permite controlar se as animações e transições de palavras-chave de dimensionamento intrínseco do CSS devem ser permitidas ou não.

O valor padrão é numeric-only, que não permite interpolação. Ao definir a propriedade como allow-keywords, você ativa as interpolações de comprimentos para palavras-chave de dimensionamento intrínseco do CSS nos casos em que o navegador pode animar essas palavras-chave.

De acordo com a especificação:

  • numeric-only: um <intrinsic-size-keyword> não pode ser interpolado.
  • allow-keywords: dois valores podem ser interpolados se um deles for <intrinsic-size-keyword> e o outro <length-percentage>. […]

Como a propriedade interpolate-size é herdada, ela pode ser declarada em :root para permitir a transição de e para as palavras-chave de dimensionamento intrínseco de todo o documento. Este é o método recomendado.

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

Na demonstração a seguir, essa regra será adicionada ao código. Como resultado, as animações de e para width: auto funcionam bem (em navegadores compatíveis):

Limitar o alcance da ativação restringindo o seletor

Se você quiser limitar a ativação de allow-keywords a apenas uma subárvore do documento, ajuste o seletor de :root para apenas o elemento que você quer segmentar. Por exemplo, se o <header> da sua página não for compatível com esse tipo de transição, você poderá limitar a ativação apenas ao elemento <main> e aos elementos filhos dele da seguinte maneira:

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

Por que não é permitido animar e redimensionar palavras-chave por padrão?

Um feedback comum sobre esse mecanismo de ativação é que os navegadores devem permitir apenas transições e animações de palavras-chave de dimensionamento intrínseco para comprimentos por padrão.

A opção de ativar esse comportamento foi pesquisada durante o desenvolvimento do recurso. O grupo de trabalho descobriu que ativar essa opção por padrão não é compatível com versões anteriores porque muitas folhas de estilo pressupõem que as palavras-chave de dimensionamento intrínseco (como auto ou min-content) não podem ser animadas. Confira os detalhes neste comentário sobre o problema relevante do Grupo de Trabalho do CSS.

Portanto, a propriedade é opcional. Graças à característica de herança, a ativação de um documento inteiro é apenas uma declaração interpolate-size: allow-sizes em :root, conforme detalhado anteriormente.

Animar para e de palavras-chave de dimensionamento intrínseco com calc-size()

Compatibilidade com navegadores

  • Chrome: 129.
  • Borda: 129.
  • Firefox: não é compatível.
  • Safari: incompatível.

Origem

Outra maneira de ativar a interpolação de e para palavras-chave de dimensionamento intrínseco é usar a função calc-size(). Ele permite que a matemática seja realizada em tamanhos intrínsecos de maneira segura e bem definida.

A função aceita dois argumentos, em ordem:

  • Uma base de tamanho de cálculo, que pode ser um <intrinsic-size-keyword>, mas também um calc-size() aninhado.
  • Um cálculo de tamanho de cálculo, que permite realizar cálculos usando a base de tamanho de cálculo. Para se referir à base de tamanho de cálculo, use a palavra-chave size.

Veja alguns exemplos:

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

Adicionando calc-size() à demonstração original, o código fica assim:

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

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

Visualmente, o resultado é exatamente o mesmo que ao usar interpolate-size. Portanto, nesse caso específico, use interpolate-size.

A capacidade de fazer cálculos é a principal vantagem do calc-size(), algo que não pode ser feito com 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 exemplo, se você quiser que todos os parágrafos de uma página sejam dimensionados para o múltiplo mais próximo de 50px, use o seguinte:

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

O calc-size() também permite interpolar entre dois calc-size()s quando as duas bases de tamanho de cálculo são idênticas. Isso também não pode ser feito com interpolate-size.

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

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

Por que não permitir <intrinsic-size-keyword> em calc()?

Uma pergunta que geralmente aparece com calc-size() é por que o grupo de trabalho do CSS não ajustou a função calc() para oferecer suporte a palavras-chave de dimensionamento intrínseco.

Uma das razões para isso é que você não pode misturar e combinar palavras-chave de dimensionamento intrínseco ao fazer cálculos. Por exemplo, você pode ficar tentado a escrever calc(max-content - min-content), que parece válido, mas na realidade não é. calc-size() garante a correção porque, ao contrário de calc(), aceita apenas um <intrinsic-size-keyword> como primeiro argumento.

Outro motivo é a consciência de contexto. Alguns algoritmos de layout têm um comportamento especial para palavras-chave de dimensionamento intrínseco específicas. calc-size() é definido explicitamente para representar um tamanho intrínseco, não um <length>. Graças a isso, esses algoritmos conseguem tratar calc-size(<intrinsic-size-keyword>, …) como <intrinsic-size-keyword>, mantendo o comportamento especial para essa palavra-chave.

Qual abordagem usar?

Na maioria dos casos, declare interpolate-size: allow-keywords em :root. É a maneira mais fácil de ativar a animação para e de palavras-chave de dimensionamento intrínseco, já que é basicamente uma linha.

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

Esse código é um aprimoramento progressivo interessante, pois os navegadores que não o suportam voltarão a não usar transições.

Quando você precisa de um controle mais refinado sobre as coisas, como fazer cálculos, ou quer usar um comportamento que só o calc-size() pode fazer, use calc-size().

#specific-element {
    width: 50px;

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

No entanto, o uso de calc-size() no código exige que você inclua substitutos para navegadores que não oferecem suporte a calc-size(). Por exemplo, adicionar declarações de tamanho extras ou retornar à detecção de recursos usando @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. */

Mais demonstrações

Confira mais algumas demonstrações que usam interpolate-size: allow-keywords a seu favor.

Notificações

A demonstração a seguir é uma bifurcação da demonstração @starting-style. O código foi ajustado para permitir a adição de itens com alturas variadas.

Para isso, toda a página ativa a interpolação de tamanho da palavra-chave, e o height em cada elemento .item é definido como auto. Caso contrário, o código é exatamente o mesmo de antes da bifurcação.

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

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

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

Animar o elemento <details>

Um caso de uso típico em que esse tipo de interpolação é usado é animar um widget de divulgação ou um acordeão exclusivo quando ele for aberto. No HTML, use o elemento <details> para isso.

Com o interpolate-size: allow-keywords, você pode fazer muito:

@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 */
        }
    }
}

No entanto, a animação só é executada quando o widget de abertura é aberto. Para isso, o Chrome está trabalhando no pseudo::details-content, que será lançado no Chrome ainda este ano e será abordado em uma postagem futura. Combinando interpolate-size: allow-keywords e ::details-content, é possível ter uma animação em ambas as direções: