Animação UX e desempenho em 2025 — Diretrizes práticas

Publicado: 20 de set. de 2025 · Tempo de leitura: 4 min · Pela equipe editorial da Unified Image Tools

Por que aposentar o GIF

GIF é ineficiente, comprime mal e tem paleta limitada. Em 2025: loops/UI → WebP/AVIF animado; vídeo real/áudio → MP4/WebM curtos.

Seleção de formato

  • Loops curtos / UI: WebP/AVIF animado
  • Foto/real + áudio: MP4/WebM (vídeo)
  • Precisa transparência: WebP animado (verifique transparência do AVIF por navegador)

Padrão de implementação

<video autoplay loop muted playsinline width="640" height="360" preload="metadata">
  <source src="/loop.webm" type="video/webm" />
  <source src="/loop.mp4" type="video/mp4" />
  <!-- Imagem de fallback -->
  <img src="/loop.jpg" alt="loop curto" />
</video>

Dicas de design de experiência

  • Respeite prefers-reduced-motion (opção de parar via CSS/JS).
  • Não bloqueie FCP/LCP: carregue perto da interação.
  • Miniatura estática; no play, carregue a animação/vídeo.

Reduzindo movimento com CSS

@media (prefers-reduced-motion: reduce) {
  .anim { animation: none !important; transition: none !important; }
}

Pensar em orçamento (desempenho/dados)

  • Hero em ~500–800KB (ou poster estático + reprodução atrasada para priorizar qualidade).
  • Evite múltiplas reproduções; concentre em 1 instância acionada pelo usuário.
  • No mobile, preload="none" por padrão; aplique src após interação.

Checklist

  • [ ] Migrar GIF → WebP/AVIF/vídeo
  • [ ] Mapear necessidades de loop/transparência
  • [ ] Suporte a prefers-reduced-motion
  • [ ] Lazy‑load e cache ajustados

Estudos de caso

  • Indicador de carregamento: trocado por CSS, ~30KB a menos.
  • Hero animado em vídeo: 2,4MB GIF → 420KB WebM (visual similar).
  • Disparo no scroll: injete src a 30% de visibilidade (IntersectionObserver).

FAQ

  • P: Em que largura devo parar o vídeo?

  • R: Considere banda/bateria; em mobile autoplay só mudo/curto. Amplie reprodução por ação do usuário.

  • P: WebP ou AVIF?

  • R: AVIF comprime melhor; inclua WebP pela compatibilidade.

  • P: Impacto em SEO?

  • R: Evite piorar LCP/CLS. Fuja de autoplay no hero; use poster estático + reprodução tardia.

  • P: Legendas e acessibilidade?

  • R: Vídeos com significado pedem legendas; ofereça teclado/parar/volume.

Princípios de projeto — Movimento com propósito

  • Significado: restrinja a mudanças de estado/causa‑efeito/hierarquia.
  • Segurança: evite >3Hz de flicker e zoom/rotação exagerados (WCAG 2.3.1).
  • Consistência: mesmo componente → mesmas durações/easing.
  • Reversibilidade: se falhar, UI estática mantém a função.

Tempos e easing — referências

  • Transições pequenas: 120–180ms / cubic-bezier(0.2, 0, 0, 1)
  • Contêiner/modal: 180–240ms / ease-out
  • Transições de página (crossfade/slide): 240–320ms / ease-in-out
  • Mola (física): damping: 20–28, stiffness: 140–220
.btn {
  transition: transform 160ms cubic-bezier(.2,0,0,1), box-shadow 160ms ease-out;
}
.btn:active { transform: translateY(1px); }

Web Animations API e mola

// Respeite Reduced Motion ao usar WAAPI
const prefersReduced = matchMedia('(prefers-reduced-motion: reduce)').matches;
const el = document.querySelector('.card');
if (!prefersReduced && el) {
  el.animate([
    { transform: 'translateY(8px)', opacity: 0 },
    { transform: 'translateY(0)', opacity: 1 },
  ], {
    duration: 220,
    easing: 'cubic-bezier(.2,0,0,1)',
    fill: 'both'
  });
}
// Aproximação simples de mola
function springTo(el, to = 0, { stiffness = 180, damping = 22 } = {}) {
  let v = 0; // velocity
  let x = 1; // normalized position
  const dt = 1/60;
  function frame() {
    const f = -stiffness * (x - to) - damping * v;
    v += f * dt;
    x += v * dt;
    el.style.transform = `translateY(${x * 20}px)`;
    if (Math.abs(v) > 0.001 || Math.abs(x - to) > 0.001) requestAnimationFrame(frame);
  }
  frame();
}

Otimização ligada ao scroll

  • Inicie com 25–33% do elemento visível (IntersectionObserver).
  • Use position: sticky; minimize cálculo JS para parallax/pinning.
  • Prefira transform/opacity a propriedades caras (top/left/width/height).
const io = new IntersectionObserver(entries => {
  entries.forEach(e => {
    if (e.isIntersecting) e.target.classList.add('in');
  });
}, { threshold: 0.3 });
document.querySelectorAll('.reveal').forEach(n => io.observe(n));

Lottie / CSS / Video — quando usar

  • Lottie (JSON vetorial): ícones/ilustrações leves; troca de cor/resolução independente.
  • CSS: micro‑interações; baixa dependência e custo de execução.
  • Vídeo: qualidade fotográfica/efeitos complexos; compressão eficiente, legendas/áudio.

Critério: “qualidade primeiro → Vídeo”, “loop UI leve → CSS/Lottie”, “transparência → Lottie/WebP animado”.

Medição de desempenho

// Entenda impactos em paint/composição
new PerformanceObserver((list) => {
  for (const e of list.getEntries()) {
    console.log(e.entryType, e.name, e.duration);
  }
}).observe({ entryTypes: ['longtask', 'measure'] });

performance.mark('anim:start');
// ... run animation ...
performance.mark('anim:end');
performance.measure('anim:duration', 'anim:start', 'anim:end');

Acessibilidade/segurança

  • Ofereça parar/pausar/ocultar (WCAG 2.2.2).
  • prefers-reduced-motion com alternativa; autoplay mudo por padrão.
  • Evite flicker entre 3Hz–60Hz; controle contraste de brilho.

Implementação à prova de falhas — checklist

  • [ ] Objetivo e KPIs (CTR/tempo/leituras …)
  • [ ] Restringir a transform/opacity (sem reflow)
  • [ ] Lazy‑load/ação do usuário como gatilho
  • [ ] Regras de Reduced Motion e UI de parada manual
  • [ ] Teste A/B para validar efeito

FAQ aprofundada

  • P: Lottie pode ficar pesado?

    • R: Reduza primitivas e otimize framerate; se pesar, migre para vídeo.
  • P: Quando GIF é inevitável?

    • R: Apenas sob fortes requisitos de compatibilidade. Aceite compromissos de tamanho/qualidade e limite o uso.
  • P: Animação ligada ao scroll engasga

    • R: Combine requestAnimationFrame e IntersectionObserver, reduza carga de thread e evite abuso de will-change.

Artigos relacionados