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; apliquesrc
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 dewill-change
.
- R: Combine