Animando Páginas Web
Visão Geral
As animações na web falham de duas maneiras: ou não executam, ou executam mal. Ambas têm causas específicas e encontráveis. Adivinhar curvas de easing é a última coisa a fazer, não a primeira.
Princípio central: antes de ajustar uma animação, prove que ela está sendo executada nos elementos que você pensa que está sendo executada.
Para o design de cenas fixas e controladas por scroll, consulte choreographing-scroll-stories. Esta skill trata da mecânica: fazer a animação funcionar e funcionar bem.
Quando uma animação não executa
Verifique nesta ordem. Os três primeiros são responsáveis por quase tudo.
| Sintoma | Causa | Verificação |
|---|---|---|
| Nada se move, elementos visíveis | O seletor corresponde a zero elementos | document.querySelectorAll(sel).length no console |
| Nada se move, elementos invisíveis | Revelação define opacity: 0 e o gatilho nunca foi acionado | opacity computada no elemento |
| Um crossfade nunca faz a transição | Ambas as camadas estão em opacity: 1; existe apenas a regra "ligado" | Deve haver uma regra de repouso definindo a camada de entrada como 0 |
| Dispara imediatamente ao carregar | Gatilho avaliado antes do layout se estabilizar | Execute após load, não DOMContentLoaded, quando as imagens dimensionam a página |
| Dispara uma vez e nunca mais | once: true com um elemento que reentra | Intencional? |
| Nada executa em uma aba oculta ou painel headless | O ticker está pausado enquanto document.hidden | Force: gsap.ticker.lagSmoothing(0); gsap.ticker.tick() |
| Gatilhos deslocados por algumas centenas de pixels | Imagens carregadas após os gatilhos serem medidos | ScrollTrigger.refresh() no load e no resize (debounce de 200 ms) |
Vale a pena detalhar o caso do crossfade porque parece correto na revisão de código:
/* Isso sozinho não faz nada. */
.tile.is-swapped .layer.is-alt { opacity: 1 }
/* O estado de repouso é o que torna uma animação. */
.layer.is-alt { opacity: 0 }
.tile.is-swapped .layer.is-alt { opacity: 1 }
Introduções e Movimento Ambiente
- O título é visível rapidamente. Reduza uma introdução até que o título apareça em cerca de 1,1 s. Uma introdução de 3,8 s é uma tela de carregamento.
- As fotos mudam desaparecendo de uma célula e aparecendo em outra. Elas nunca deslizam. Cada foto é um elemento, nunca na tela duas vezes e nunca desaparecendo enquanto sua cópia aparece.
- A mudança ambiente é lenta e irregular. Um bloco a cada 1,8 a 3,4 s parece uma grade viva, enquanto qualquer bloco individual muda lentamente. Não encurte o intervalo para fazer cada bloco mudar mais rápido.
- Embaralhamento em rodadas. Cada bloco muda uma vez antes de qualquer repetição, e cada escolha é da metade mais distante dos últimos três.
- Pausa o que ninguém pode ver. Os loops ambiente param fora da tela e em uma aba oculta e retomam de onde pararam.
- As introduções não se repetem em cada retorno. Após a introdução se estabilizar, nada além da camada ambiente se move.
Revelações ao Scroll
- Defina
opacity: 0apenas em elementos que estão realmente abaixo da dobra no carregamento. Ocultar tudo de forma geral significa que qualquer coisa acima da dobra fica invisível até um scroll que pode nunca acontecer. - Revele em
top 88%, não na interseção completa. Esperar o elemento estar totalmente visível significa que ele anima depois que o leitor já está olhando para ele. - Espaçamento entre irmãos, não dentro de um componente. 60-90ms entre cards, 0 dentro de um card.
- Agrupe com um observador por grupo em vez de um por elemento.
Vídeo
O vídeo de fundo com reprodução automática tem um bug universal: o vídeo é reproduzido fora da tela, um observador o pausa, e quando o leitor chega, o loop já está 15 segundos adiantado e o batimento inicial se perdeu.
Remova o atributo autoplay completamente.
pause() e load() na inicialização.
Na primeira interseção apenas: currentTime = 0, depois play().
Sirva um arquivo 1x abaixo de 992px ou abaixo de 1,5 de proporção de pixels do dispositivo, e um frame de poster para a pintura antes da decodificação do vídeo. Vídeo pesado na página inicial também é onde a largura de banda é gasta: audite-o.
Gaguejo
A deriva lenta em uma fotografia parada é a causa mais comum. Um movimento Ken Burns de menos de cerca de 1px por quadro reamostra a imagem a cada quadro e parece cintilação, não movimento.
- Ou mova o suficiente para ser movimento, ou não mova nada.
- Para uma pausa em um vídeo renderizado, copie o quadro codificado byte por byte em vez de recodificar um quadro idêntico. Qualquer outra coisa deriva.
- Verifique a emenda do loop. Se o quadro 0 e o quadro final diferirem, o loop salta visivelmente a cada ciclo. Meça a diferença absoluta média entre eles.
- Não duplique a suavização. Uma biblioteca de scroll suave mais um valor pesado de
scrubresulta em uma página que fica atrás da roda. Com uma biblioteca de scroll suave presente, usescrub: 0.2.
Sticky
position: sticky dentro de um grid CSS resolve em relação à área do grid, não ao contêiner inteiro. Um item sticky que tem apenas a altura do seu próprio conteúdo não tem deslocamento e libera imediatamente.
.grid { align-items: stretch } /* a área agora tem altura total */
.col { position: static; height: 100% }
.inner { position: sticky; top: 10rem }
Fallbacks
Toda animação recebe três fallbacks, documentados na especificação de movimento e testados:
- Movimento reduzido.
prefers-reduced-motion: reducee uma consulta?staticrenderizam os estados empilhados e finalizados: nada fixo, nada oculto, cada cena em seu estado final. Cubra também cadatransitionCSS que você adicionou. Listar os seletores animados, mas esquecer a transição no contêiner, deixa uma cor ainda deslizando. - Sem script. Tudo permanece visível. Uma classe é adicionada após cerca de 3,5 s se o script nunca iniciou, e o CSS revela tudo abaixo dela.
- Celulares. Cenas fixas empilham. Declare isso, teste isso.
Verificação
Não relate que uma animação está funcionando a partir do código. Confirme na página:
- As contagens de seletores são diferentes de zero para cada grupo animado
- A
opacitycomputada em um elemento acima da dobra é 1 antes de qualquer scroll - O
currentTimedo vídeo está abaixo de 0,5 s no momento em que se torna visível pela primeira vez - A emulação de movimento reduzido deixa a página completamente legível e estática
- A ausência de script deixa tudo visível
- Nada anima
width,height,topouleft; usetransformeopacity - Os loops ambiente não fazem alterações de estilo enquanto estão fora da tela
Bandeiras Vermelhas
- Ajustar duração ou easing antes de confirmar que o seletor corresponde
- Uma regra
.is-activesem uma regra de estado de repouso correspondente transition: all- Uma animação adicionada porque a seção "parecia vazia"
- Uma foto que desliza
- A mesma foto na tela duas vezes