Animando layouts de CSS Grid com CSS Anchor Positioning
Quando o espaço disponível muda, um grid comum reposiciona as células de um salto. Dá para fazer essa reorganização acontecer com animação, e com animações interrompíveis, usando CSS Anchor Positioning e transições comuns. Sem JavaScript e sem View Transitions.
No CSS Day, em junho, alguém perguntou ao autor como o Brave (o navegador) teria construído a New Tab Page dele, que exibe alguns tiles dispostos em grid.
O grid em si não tem nada de espetacular. O especial acontece ao redimensionar a viewport: conforme o espaço disponível muda, o grid se reorganiza de forma animada. As animações também são interrompíveis, o que indica que não há View Transitions em jogo.
Como eles fizeram? Não se sabe. Mas, para recriar o efeito, o caminho seria CSS Anchor Positioning com CSS Transitions comuns por cima.
Quem ainda não conhece Anchor Positioning pode começar pelo post Anchors Aweigh!, que explica tudo o que é necessário sobre o recurso (com vídeo).
A técnica
O efeito desejado é este: conforme o grid é redimensionado, as células animam até a nova posição.
O truque é usar CSS Anchor Positioning e ancorar o conteúdo da célula na própria célula. Como não é possível ancorar conteúdo diretamente numa célula de grid (células de grid são conceitos virtuais), é preciso injetar uma div extra que ocupe o espaço disponível da célula e sirva de âncora.
As transições CSS comuns ficam responsáveis por animar as coisas conforme elas se deslocam.
<div class="grid"> <div class="cell"> <div class="content">CONTENT</div> </div> <div class="cell"> <div class="content">CONTENT</div> </div> …</div>.grid { display: grid; grid-template-columns: repeat(auto-fill, 6rem); gap: 1em;}
.cell { height: auto; aspect-ratio: 1;
anchor-scope: --a; anchor-name: --a;}
.content { position: absolute; position-anchor: --a; inset: anchor(inside); width: 6rem; height: 6rem;
transition: inset 0.2s ease;}São quatro detalhes que fazem o arranjo funcionar:
- Como nomes de âncora são globais por padrão, eles precisam ficar restritos à subárvore de cada célula. O
anchor-scope: --a;cuida disso, permitindo que cada.cellreutilize o identificador--asem conflitar com as outras. - O
aspect-ratio: 1;nas.cellgarante que elas tenham tanta altura quanto largura. - A declaração de
transitiongarante que oinsetfaça a transição suave de um valor ao outro. - As divs
.contentrecebem dimensões explícitas (widtheheight). Sem isso, elas esticariam e comprimiriam durante a interpolação, conforme o grid é redimensionado.
Ao contrário das View Transitions, esta técnica não exige JavaScript nenhum e as animações são perfeitamente interrompíveis.
Ao redimensionar a viewport rapidamente para frente e para trás, os tiles não entram em fila esperando frames de animação ou promises de transição terminarem: redirecionam na hora, em pleno voo, para a posição recém-calculada.
Uma ressalva: por ora, isso não funciona no Firefox, porque ele não faz interleaving de estilo e layout para Anchor Positioning. Em vez de resolver para valores em pixel interpoláveis, ele mantém o valor de anchor() no computed value time.
Demonstração
Esta é a demonstração construída para mostrar o comportamento:
Redimensione o embed, ou o .container dentro da própria demonstração, para ver o grid se reorganizar.
Para algo de fidelidade maior (e mais próximo da New Tab Page do Brave):
Se este post pareceu familiar
O autor já havia compartilhado uma demonstração no Bluesky sobre o assunto em junho, e só escreveu o post depois.
Chris Coyier também cobriu a abordagem no Frontend Masters. No texto dele, ele relembra como o Masonry.js animava layouts fluidos e mostra como esse código traz aquele mesmo comportamento nativamente para o CSS: Masonry (with Animation) in CSS.