Pular para o conteúdo
Gustavo Tutorial

Movimento

Animação boa quase nunca é questão de biblioteca — é questão de tempo, curva e contenção. Esta trilha cobre os princípios, o que o CSS moderno já faz sem JavaScript, quando vale mesmo instalar algo, e por que movimento demais derruba conversão.

Revisão já conversamos sobre isso Novo ainda não vimos 9 partes · 37 tópicos

Nenhum tópico com esse filtro.

PARTE 1

Princípios

O que separa animação amadora de profissional quase nunca é a biblioteca. É tempo, curva e contenção.

Por que animar

Novo

Animação sem função vira ruído. Antes de qualquer efeito, a pergunta é qual destes quatro papéis ele cumpre.

PapelO que resolveExemplo
RetornoConfirmar que a ação foi registradaBotão que afunda ao clique
ContinuidadeMostrar de onde veio e para onde foiCard que cresce e vira a página inteira
AtençãoDizer o que olhar primeiroEntrada em sequência de uma lista
PersonalidadeDar caráter à marcaUma mola mais solta, um tempo mais lento
O teste: se você desligar a animação e nada piorar de entendimento, ela existe só por enfeite — e aí precisa ser discreta o suficiente para não custar atenção nem desempenho.

Duração: as faixas que funcionam

NovoCSS

Tempo errado é o erro mais comum e o mais fácil de corrigir. A regra geral: quanto maior o elemento e maior a distância percorrida, mais longa a animação.

150ms · retorno ao toque
250ms · menu, abrir e fechar
400ms · entrada de seção
800ms · lento demais para interface
A mesma curva, quatro durações--saida-suave
SituaçãoFaixa
Hover, foco, troca de cor100–200ms
Botão, ícone, elemento pequeno150–250ms
Card entrando, menu abrindo250–400ms
Seção inteira, modal, página400–600ms
Qualquer coisa acima de 700msQuase sempre lento demais

Duas assimetrias que quase ninguém aplica e fazem diferença grande: sair é mais rápido que entrar (a pessoa já decidiu, não precisa ver a saída), e o que responde a um clique é mais rápido que o que acontece sozinho.

Curva e mola

NovoCSS

A curva define a aceleração ao longo do tempo. É ela que faz um movimento parecer natural ou robótico.

as curvas que resolvem quase tudo
/* entrada — começa rápido, freia no fim.
   É a curva que dá sensação de "chegou e assentou" */
--saida-suave: cubic-bezier(.22, .61, .36, 1);

/* saída — sai acelerando, não precisa frear */
--entrada-rapida: cubic-bezier(.4, 0, 1, 1);

/* dos dois lados, para movimento contínuo */
--padrao: cubic-bezier(.4, 0, .2, 1);

/* NUNCA use linear em movimento de interface.
   Só serve para rotação contínua e barra de progresso. */
linear
ease-in · --entrada-rapida
saída suave · --saida-suave
com passada · mola
A mesma distância, no mesmo tempo — só a curva muda900ms cada

Mola

Mola (spring) não é definida por duração e curva, e sim por rigidez, amortecimento e massa — como um objeto físico. O resultado tem uma pequena ultrapassagem antes de assentar, e é isso que dá a sensação de "vivo" que se vê em sites como o da biblioteca Motion.

O CSS não tem mola nativa; é o principal motivo real para usar biblioteca de animação. Dá para imitar com cubic-bezier de valores acima de 1, mas é aproximação.

mola aproximada em CSS puro
.botao {
  transition: transform .4s cubic-bezier(.34, 1.56, .64, 1);
}
.botao:hover { transform: scale(1.04); }
/* o 1.56 é o que faz passar do ponto e voltar */
Mola tem hora. Ela combina com marca descontraída e com elemento pequeno. Num site de advogado ou de clínica, o mesmo efeito lê como falta de seriedade. Curva sóbria é o padrão seguro; mola é escolha de personalidade.

Encadeamento

NovoCSS

Quando vários elementos entram juntos, entrar ao mesmo tempo parece bloco caindo. Com um atraso pequeno entre eles, vira sequência — e a sequência guia o olho na ordem que você quer.

css puro, sem JS
.card { animation: surgir .5s cubic-bezier(.22,.61,.36,1) both; }

.card:nth-child(1) { animation-delay: 0ms; }
.card:nth-child(2) { animation-delay: 60ms; }
.card:nth-child(3) { animation-delay: 120ms; }
.card:nth-child(4) { animation-delay: 180ms; }
Encadeamento: 40ms entre um e outroanimation-delay

O atraso ideal costuma ficar entre 40 e 80ms. Abaixo disso não se percebe; acima de 120ms a lista parece lenta, e com muitos itens o último demora demais para aparecer.

Com muitos itens, encadeie só os primeiros. Doze cards com 80ms de atraso somam quase um segundo até o último. Aplicar o atraso apenas aos quatro primeiros mantém o efeito sem a espera.

A contenção é o que parece caro

Novo

Site com animação em tudo não parece sofisticado — parece inseguro. Os sites que impressionam por movimento quase sempre concentram o efeito em um ou dois momentos e deixam o resto quieto.

Três regras que seguram a mão

  • Um momento de destaque por página. Se tudo chama atenção, nada chama.
  • Distância curta. Elemento que sobe 16 a 24 pixels parece intencional; subindo 100, parece que estava fora do lugar.
  • Nunca anime o que a pessoa precisa ler agora. Texto que aparece depois é texto que alguém não leu.
Conteúdo não pode depender de animação. Se o CSS deixa o bloco em opacity: 0 esperando o JavaScript, e o JavaScript falha, o conteúdo some — para o visitante e para o buscador. O estado final deve ser o padrão; o estado inicial se aplica só quando o script confirma que assumiu.
PARTE 2

O que o CSS já resolve

Boa parte do que se faz com biblioteca hoje é CSS nativo. Sem peso, sem dependência, e rodando fora da thread principal.

Transição e keyframes

RevisãoCSS

transition suaviza a passagem entre dois estados; @keyframes define uma sequência própria. Vale reforçar uma prática: declare a transição no estado normal, não no :hover — assim ela vale na ida e na volta.

css
/* certo — vale nos dois sentidos */
.botao { transition: transform .2s var(--padrao); }
.botao:hover { transform: translateY(-2px); }

/* errado — só anima ao entrar, volta seco */
.botao:hover { transform: translateY(-2px); transition: transform .2s; }

Animação por rolagem, sem JavaScript

RevisãoCSS

O reveal ao rolar — que historicamente exige IntersectionObserver ou uma biblioteca — virou CSS nativo, e roda na thread de composição, sem travar mesmo em aparelho fraco.

css
@keyframes surgir {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: translateY(0); }
}

.secao {
  animation: surgir linear both;
  animation-timeline: view();
  animation-range: entry 15% cover 40%;
}

/* barra de progresso de leitura */
.progresso {
  animation: encher linear;
  animation-timeline: scroll();
  transform-origin: left;
}
@keyframes encher { from { scale: 0 1; } to { scale: 1 1; } }
#001
#002
#003
#004
#005
Paralaxe: role dentro do palcoo número anda mais devagar que a foto

view() atrela a animação à entrada do elemento na tela; scroll() atrela à rolagem da página inteira. O animation-range define em que ponto da entrada o movimento começa e termina.

Suporte ainda é desigual. Chrome e Edge sim; os demais chegaram depois. Como é animação, degradar é seguro: onde não funciona, o elemento simplesmente aparece pronto. Mas isso só vale se o estado final for o padrão — veja a regra da Parte 1.

Animar entrada e saída de elementos

NovoCSS

Esse era o buraco clássico do CSS: não dava para animar um elemento que acabou de ser criado, nem segurar um que está sendo removido. Era o motivo mais comum de recorrer a biblioteca. Hoje existem três peças que resolvem.

css
.painel {
  opacity: 1;
  transition: opacity .3s, display .3s allow-discrete;
}

/* estado de partida, no primeiro quadro em que ele existe */
@starting-style {
  .painel { opacity: 0; }
}

/* ao sair */
.painel[hidden] { opacity: 0; }
PeçaO que faz
@starting-styleDefine de onde o elemento parte quando aparece
allow-discretePermite animar propriedades que antes trocavam de uma vez, como display
transition-behaviorControla esse comportamento de forma explícita

Com isso, um modal em <dialog> ou um menu com popover ganha entrada e saída animadas sem uma linha de JavaScript.

View Transitions

RevisãoCSS

Transição entre estados ou entre páginas, com o navegador calculando o caminho. É o recurso que mais aproxima um site estático da sensação de aplicativo — e são duas linhas.

css
@view-transition { navigation: auto; }

/* o elemento que "viaja" entre as duas páginas */
.capa-projeto { view-transition-name: capa; }

/* ajustando o tempo da transição */
::view-transition-old(root),
::view-transition-new(root) { animation-duration: .35s; }

Com o mesmo view-transition-name nas duas páginas, o navegador entende que é o mesmo elemento e anima a passagem — a foto do card cresce e vira a foto do topo da página seguinte. Num portfólio, o efeito é notável e o custo é quase zero.

PARTE 3

Ferramentas de JavaScript

Quando o CSS não dá conta — e o que cada biblioteca cobra em troca.

Quando vale sair do CSS

Novo

Três situações justificam biblioteca. Fora delas, o CSS resolve mais barato.

  • Mola de verdade. Física, com rigidez e amortecimento, respondendo à velocidade do gesto. O CSS só aproxima.
  • Sequência complexa e sincronizada. Uma linha do tempo com dez passos encadeados, que precisa ser pausada, revertida ou controlada por código.
  • Animação dirigida por dado. Valores que mudam em tempo real — arrastar, gesto, número que sobe conforme um cálculo.

As bibliotecas, em ordem de peso

NovoJS
FerramentaO que éQuando usar
CSS puroZero pesoA maioria absoluta dos casos
Web Animations APINativa do navegador, controlada por JSPrecisa de controle mas não quer dependência
Motion (motion.dev)Antigo Framer Motion, hoje independente, com versão em JavaScript puro além da de React. Molas de verdade, API enxutaQuando a mola e o encadeamento são o ponto
GSAPO veterano. Linha do tempo poderosa, controle fino, muitos pluginsSequências longas e sincronizadas
LottieRoda animação exportada do After EffectsIlustração animada vinda de designer
a API nativa, que já existe
// Web Animations API — sem biblioteca nenhuma
card.animate(
  [
    { opacity: 0, transform: 'translateY(20px)' },
    { opacity: 1, transform: 'translateY(0)' }
  ],
  { duration: 400, easing: 'cubic-bezier(.22,.61,.36,1)', fill: 'both' }
);
Antes de instalar qualquer coisa, pergunte: o que exatamente o CSS não faz aqui? Na maioria dos projetos de landing page a resposta é "nada" — e aí a biblioteca só adiciona peso, dependência que envelhece e uma etapa de build que você não tinha.

Rolagem suave: cuidado

Novo

Bibliotecas de rolagem suave — Lenis é a mais conhecida — sequestram a rolagem da página para dar inércia. É um efeito bonito em vitrine de estúdio e problemático em site de serviço.

O que elas custam

  • Quebram a rolagem nativa que a pessoa conhece, incluindo a velocidade que ela configurou no sistema.
  • Atrapalham navegação por teclado e leitor de tela.
  • Adicionam atraso entre o gesto e a resposta — que é exatamente a sensação de site travado.
  • Costumam conflitar com position: sticky e com âncoras.
Para ancoragem suave entre seções, o CSS já resolve com scroll-behavior: smooth — e respeitando prefers-reduced-motion. Isso cobre o caso legítimo sem nenhum dos custos acima.
PARTE 4

Performance

Animação que engasga é pior que animação nenhuma. O que trava, por quê, e como testar de verdade.

Anime duas propriedades

RevisãoCSS

O navegador desenha a tela em etapas: calcular posições, pintar e compor. transform e opacity mexem só na última — que roda na placa de vídeo. Tudo o mais força recalcular a página inteira, a cada quadro.

Em vez de animarAnime
left, top, margintransform: translate()
width, heighttransform: scale()
displayopacity + allow-discrete
Sombra pesada em movimentoUma camada com sombra pronta, variando a opacidade

Sobre will-change: ele avisa o navegador para preparar o elemento, e usado em excesso consome memória e piora tudo. Aplique em poucos elementos, e só quando houver engasgo medido — não preventivamente.

Testar onde dói

Novo

Seu computador roda qualquer animação a 60 quadros por segundo. O celular de quatro anos do cliente do seu cliente, não. Testar no lugar errado é o que faz animação travada chegar em produção.

  • DevTools → Performance, com limitação de CPU em 4x ou 6x. Simula aparelho fraco no seu próprio computador.
  • Aba Rendering → Frame Rendering Stats mostra a taxa de quadros ao vivo enquanto você rola.
  • Um celular de verdade, de preferência antigo. Nada substitui.
O sintoma clássico: a página abre bem e engasga ao rolar. Quase sempre é animação disparada por scroll sem requestAnimationFrame, ou muitos elementos observados ao mesmo tempo sem unobserve depois que já animaram.
PARTE 5

Acessibilidade e conversão

Movimento afeta gente de verdade — e afeta o resultado da página. Duas coisas que raramente entram na conversa sobre animação.

prefers-reduced-motion não é opcional

RevisãoCSS

Parte das pessoas sente enjoo, tontura ou desconforto real com movimento na tela — é chamado de sensibilidade vestibular, e quem tem costuma já ter desligado animação no sistema. A media query existe para respeitar essa escolha.

o mínimo, em todo projeto
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

Uma abordagem mais cuidadosa não desliga tudo: mantém mudanças de cor e opacidade, que raramente incomodam, e elimina só deslocamento, escala e paralaxe — que são os que causam desconforto.

O que mais incomoda, em ordem: paralaxe, elementos grandes atravessando a tela, zoom automático, rotação e piscar. Fade e mudança de cor são quase sempre seguros.

Quando animação derruba conversão

Novo

Numa página institucional, animação demais é questão de gosto. Numa página que precisa gerar contato, ela custa dinheiro.

PráticaPor que custa
Hero que anima antes de mostrar o textoOs primeiros segundos decidem se a pessoa fica — e ela passa esperando
Conteúdo que só aparece ao rolarQuem chega e não vê nada acha que a página quebrou
Paralaxe pesada no celularEngasga justamente onde está a maioria do tráfego
Botão com animação chamativa demaisLê como anúncio, e anúncio a pessoa ignora por reflexo
Rolagem sequestradaTira o controle de quem quer só achar o telefone
A regra que resolve: tudo que for essencial para decidir — o que é, para quem, quanto custa, como falar com você — aparece imediatamente, sem depender de rolagem nem de espera. O movimento entra no que é secundário.
PARTE 6

Receitas

Os efeitos que aparecem em quase todo projeto, prontos, em CSS puro ou com o mínimo de JavaScript.

Seis efeitos prontos

NovoCSS

1 · Reveal ao rolar, com degradação segura

css
/* estado final é o padrão — se o JS falhar, aparece normal */
[data-reveal] { opacity: 1; }

/* o estado inicial só vale quando o JS confirma que assumiu */
.js [data-reveal] {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity .6s var(--saida-suave),
              transform .6s var(--saida-suave);
}
.js [data-reveal].is-in { opacity: 1; transform: none; }
js
document.documentElement.classList.add('js');

var obs = new IntersectionObserver(function (ents) {
  ents.forEach(function (e) {
    if (!e.isIntersecting) return;
    e.target.classList.add('is-in');
    obs.unobserve(e.target);
  });
}, { threshold: .15 });

document.querySelectorAll('[data-reveal]').forEach(function (el) {
  obs.observe(el);
});

role aqui dentro ↓

Reveal ao rolar — role dentro do palcoIntersectionObserver

2 · Menu que abre com altura automática

css
/* grid resolve o problema clássico de animar height: auto */
.painel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows .3s var(--padrao);
}
.painel.is-aberto { grid-template-rows: 1fr; }
.painel > div { overflow: hidden; }

Estrutura, equipe e insumos. O cardápio é montado com você, no orçamento.

Sim — o deslocamento entra no orçamento, separado do resto.

Entrada para reservar a data e o restante até a semana do evento.

Altura automática animadagrid-template-rows: 0fr → 1fr

3 · Botão com retorno ao toque

css
.botao {
  transition: transform .15s var(--padrao), background .2s;
}
.botao:hover  { transform: translateY(-2px); }
.botao:active { transform: translateY(0) scale(.98); transition-duration: .05s; }
parece que não ouviu
afunda ao apertar
Clique e segure os dois:active com transition curta

4 · Contador que anima ao aparecer

js
function contar(el) {
  var alvo = Number(el.dataset.num), inicio = performance.now(), dur = 1400;
  if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
    el.textContent = alvo; return;
  }
  (function passo(agora) {
    var t = Math.min((agora - inicio) / dur, 1);
    el.textContent = Math.round(alvo * (1 - Math.pow(1 - t, 3)));
    if (t < 1) requestAnimationFrame(passo);
  })(inicio);
}
0número de exemplo, contando do zero
Contador que anima ao aparecerdesacelera no fim (ease-out)

5 · Imagem que cresce no hover, sem estourar

css
.moldura { overflow: hidden; border-radius: 6px; }
.moldura img {
  transition: transform .5s var(--saida-suave);
  will-change: transform;
}
.moldura:hover img { transform: scale(1.05); }
sem moldura: estoura
com overflow: hidden
Passe o mouse nas duas fotosa moldura segura o zoom

6 · Barra de progresso de leitura

css puro, sem JS
.progresso {
  position: fixed; top: 0; left: 0; right: 0; height: 3px;
  background: var(--destaque); transform-origin: left;
  animation: encher linear;
  animation-timeline: scroll();
}
@keyframes encher { from { scale: 0 1; } to { scale: 1 1; } }

Uma página de serviço é lida de cima para baixo, e quase ninguém chega ao fim.

A barra no topo diz quanto falta — e quem vê que falta pouco, continua.

Ela acompanha a rolagem deste palco, não da página: role aqui dentro.

Em CSS moderno, animation-timeline: scroll() faz isso sem uma linha de JavaScript.

Onde o navegador ainda não entende, a barra simplesmente não aparece — e nada quebra.

É o tipo de detalhe que ninguém elogia e todo mundo sente.

Uma página de serviço é lida de cima para baixo, e quase ninguém chega ao fim.

A barra no topo diz quanto falta — e quem vê que falta pouco, continua.

Ela acompanha a rolagem deste palco, não da página: role aqui dentro.

Em CSS moderno, animation-timeline: scroll() faz isso sem uma linha de JavaScript.

Onde o navegador ainda não entende, a barra simplesmente não aparece — e nada quebra.

É o tipo de detalhe que ninguém elogia e todo mundo sente.

Barra de progresso — role dentro do palcoanimation-timeline: scroll()
Todas usam as variáveis de curva da Parte 1. Declare-as uma vez no :root e o movimento do site inteiro fica coerente — é a mesma lógica de token que vale para cor e espaçamento.
PARTE 7

As duas bibliotecas

Motion e GSAP são as duas que sobraram depois que o CSS moderno absorveu quase tudo. Esta parte é a decisão entre elas e o que você precisa saber de GSAP. A API do Motion inteira está na trilha Referência: Motion — aqui ela só aparece no que importa para escolher.

Antes de instalar qualquer uma

Novo

As cinco partes anteriores foram sobre o que o CSS já faz. Este é o momento em que a resposta deixa de ser "CSS" — e são poucos casos.

Você precisa deBiblioteca?
Mola de verdade, reagindo a gestoSim — o CSS não tem mola
Cascata numa lista de tamanho desconhecidoSim — nth-child não dá conta
Interromper preservando velocidadeSim — em CSS a transição reinicia
Sequência longa, montada em partesSim — e aqui o GSAP ganha
Seção presa na rolagem contando uma históriaSim — ScrollTrigger com pin
Reveal ao rolar, hover, transição entre páginasNão. CSS faz melhor e de graça.
Em landing page de serviço, a resposta honesta costuma ser "nenhuma". O peso, a dependência que envelhece e o risco de o CDN cair são custos reais, cobrados de um cliente que nunca vai notar a diferença. Instale quando a linha da tabela for verdadeira — não porque a biblioteca é boa.

Motion, em uma página

NovoJS

O que era Framer Motion, hoje independente e com versão em JavaScript puro — que é a que serve para HTML único. É a mais leve das duas e a melhor em mola.

o essencial
<script src="https://cdn.jsdelivr.net/npm/motion@13/dist/motion.js"></script>

const { animate, scroll, inView, stagger } = Motion;

animate('.cta', { scale: [0.9, 1] }, { type: 'spring', bounce: 0.3 });
animate('.card', { opacity: [0, 1], y: [20, 0] }, { delay: stagger(0.06) });
inView('.secao', el => animate(el, { opacity: [0, 1] }));
scroll(animate('.barra', { scaleX: [0, 1] }, { ease: 'linear' }));
Quatro linhas cobrem a maior parte do uso real. Opções de tempo, as duas formas de mola, a gramática do offset, o objeto de controle e o equivalente em React estão na trilha Referência: Motion — 8 partes, com índice de API e filtro.

O que é, e a mudança de licença

NovoJS

O GSAP existe há muito mais tempo que qualquer biblioteca atual e continua sendo a referência quando o assunto é sequência complexa. Por anos, os plugins mais interessantes eram pagos — o que afastava quem fazia site de cliente pequeno.

Isso mudou: depois da aquisição pela Webflow, a biblioteca e todos os plugins passaram a ser gratuitos, inclusive para uso comercial. Na prática, o ScrollTrigger — que é o motivo principal de usar GSAP — está liberado.

via CDN
<script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/ScrollTrigger.min.js"></script>
<script>
  gsap.registerPlugin(ScrollTrigger);
</script>
Peso: o núcleo do GSAP mais o ScrollTrigger somam bem mais que o Motion. Para uma landing page com dois efeitos, é caro. Para um site que se sustenta em movimento — portfólio, apresentação de produto, página de evento — o custo se paga.

to, from e fromTo

NovoJS
js
// do estado atual PARA o que você descrever
gsap.to('.card', { opacity: 1, y: 0, duration: 0.6, ease: 'power3.out' });

// DE um estado até o atual — o mais útil para entrada
gsap.from('.card', { opacity: 0, y: 24, duration: 0.6, stagger: 0.06 });

// de um estado explícito até outro
gsap.fromTo('.selo',
  { scale: 0.8, opacity: 0 },
  { scale: 1, opacity: 1, duration: 0.4, ease: 'back.out(1.7)' }
);

O gsap.from é o que mais aparece em animação de entrada, e tem uma vantagem sobre o CSS: o estado final é o que está escrito na folha de estilo. Se o script não rodar, o elemento fica visível — a degradação segura vem de graça.

CurvaSensação
power2.out a power4.outEntrada que freia. O padrão para quase tudo
power2.inSaída que acelera
back.out(1.7)Passa do ponto e volta — parecido com mola
elastic.outOscila bastante. Use com muita parcimônia
noneLinear. Só para rotação contínua

Timeline — o verdadeiro diferencial

NovoJS

É aqui que o GSAP não tem concorrente. Uma linha do tempo encadeia animações em sequência, e você controla a posição de cada uma em relação às outras — sem calcular atraso na mão.

entrada orquestrada da hero
const tl = gsap.timeline({ defaults: { ease: 'power3.out', duration: 0.6 } });

tl.from('.hero__rotulo', { opacity: 0, y: 12 })
  .from('.hero__titulo', { opacity: 0, y: 24 }, '-=0.35')  // sobrepõe 0,35s
  .from('.hero__sub',    { opacity: 0, y: 18 }, '-=0.4')
  .from('.hero__cta',    { opacity: 0, scale: .94 }, '-=0.3')
  .from('.hero__selo',   { opacity: 0 }, '<');             // junto com o anterior
PosiçãoSignifica
omitidaDepois que a anterior terminar
'-=0.3'Começa 0,3s antes do fim da anterior
'+=0.2'Espera 0,2s depois da anterior
'<'Ao mesmo tempo que a anterior
0.5No segundo 0,5 da linha do tempo

E a linha do tempo inteira pode ser pausada, revertida, acelerada ou controlada por rolagem — coisa que encadeamento de animation-delay em CSS não permite.

ScrollTrigger

NovoJS

O plugin que faz o GSAP valer o peso. Ele liga qualquer animação à posição da rolagem, e resolve o efeito de prender a seção enquanto a animação acontece — que é a base daqueles sites onde a tela fica parada e o conteúdo se transforma.

reveal simples
gsap.from('.secao__card', {
  opacity: 0, y: 30, duration: 0.6, stagger: 0.08,
  scrollTrigger: {
    trigger: '.secao',
    start: 'top 80%',        // topo da seção a 80% da altura da janela
    toggleActions: 'play none none none'
  }
});
seção presa contando o cronograma
const etapas = gsap.timeline({
  scrollTrigger: {
    trigger: '.cronograma',
    start: 'top top',
    end: '+=2000',      // dura 2000px de rolagem
    pin: true,          // prende a seção na tela
    scrub: 1,           // segue a rolagem, com 1s de suavização
    markers: false      // true durante o desenvolvimento
  }
});

etapas.from('.semana-1', { opacity: 0, x: -40 })
      .from('.semana-2', { opacity: 0, x: -40 })
      .from('.semana-3', { opacity: 0, x: -40 })
      .from('.semana-4', { opacity: 0, x: -40 });
OpçãoO que faz
triggerO elemento cuja posição define o início
start / endOnde começa e termina: 'top 80%', '+=2000'
scrubtrue cola na rolagem; número adiciona suavização
pinPrende o elemento enquanto o gatilho está ativo
toggleActionsO que fazer ao entrar, sair, voltar e sair de volta
markersMostra as linhas de início e fim. Indispensável para ajustar
Ligue markers: true enquanto ajusta e desligue antes de publicar. É o erro de distração mais comum com ScrollTrigger — as linhas coloridas vão para produção e ficam por meses.
Cuidado com pin no celular. Prender seção em tela pequena costuma atrapalhar mais que impressionar, e quem quer só achar o telefone fica preso rolando. Vale envolver o efeito numa checagem de largura e desligar abaixo de 900px.

Motion ou GSAP

Novo
CritérioCSS puroMotionGSAP
PesoZeroPequenoMaior, ainda mais com plugins
Mola físicaSó aproximaçãoSim, é a especialidadeSim, via curvas e plugin
Sequência longaDifícilPossívelMelhor da categoria
Prender seção na rolagemNãoNão prontoSim, com ScrollTrigger
Curva de aprendizadoCurtaCurtaMédia
Precisa de buildNãoNão, via CDNNão, via CDN
A regra que eu seguiria: CSS até não dar mais. Se o que falta é mola ou gesto, Motion. Se o que falta é uma narrativa presa à rolagem, com seção fixa e vários passos sincronizados, GSAP com ScrollTrigger. Usar os dois no mesmo projeto raramente se justifica.
PARTE 8

Catálogo para copiar

Os efeitos que mais aparecem, cada um nas três formas — para você comparar o custo e escolher. Todos respeitam prefers-reduced-motion e degradam sem quebrar.

A base que todos usam

NovoCSS

Declare as curvas uma vez, como token, e o movimento do site inteiro fica coerente.

no :root
:root {
  --saida:  cubic-bezier(.22, .61, .36, 1);   /* entrada que freia  */
  --padrao: cubic-bezier(.4, 0, .2, 1);       /* uso geral          */
  --mola:   cubic-bezier(.34, 1.56, .64, 1);  /* passa e volta      */
  --t-rapido: .15s;
  --t-medio:  .3s;
  --t-lento:  .55s;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}
a checagem, para as versões com JavaScript
const semMovimento = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
// envolva qualquer animação por script:  if (semMovimento) return;

1 · Reveal ao rolar

Novo
css puro — nenhuma dependência
@keyframes surgir {
  from { opacity: 0; transform: translateY(22px); }
  to   { opacity: 1; transform: none; }
}
.revela {
  animation: surgir .55s var(--saida) both;
  animation-timeline: view();
  animation-range: entry 12% cover 38%;
}
motion
inView('.revela', (el) => {
  animate(el, { opacity: [0, 1], y: [22, 0] },
             { duration: .55, ease: [.22,.61,.36,1] });
}, { amount: .2 });
gsap
gsap.utils.toArray('.revela').forEach((el) => {
  gsap.from(el, {
    opacity: 0, y: 22, duration: .55, ease: 'power3.out',
    scrollTrigger: { trigger: el, start: 'top 85%' }
  });
});
Escolha: CSS. É o caso em que a versão nativa é melhor que as duas bibliotecas — roda fora da thread principal e não custa nada. Use JavaScript só se precisar de suporte em navegador antigo.

2 · Lista entrando em sequência

Novo
css
.card { animation: surgir .5s var(--saida) both; }
.card:nth-child(1) { animation-delay: 0ms;   }
.card:nth-child(2) { animation-delay: 60ms;  }
.card:nth-child(3) { animation-delay: 120ms; }
.card:nth-child(n+4) { animation-delay: 180ms; }  /* o resto junto */
motion
animate('.card', { opacity: [0,1], y: [22,0] },
        { duration: .5, delay: stagger(.06) });
gsap
gsap.from('.card', { opacity: 0, y: 22, duration: .5, stagger: .06 });
Escolha: se a lista tem número fixo e pequeno, CSS. Se o número vem de dado e varia, biblioteca — porque escrever nth-child para uma lista de tamanho desconhecido não funciona.

3 · Botão com retorno físico

Novo
css — mola aproximada
.cta {
  transition: transform var(--t-rapido) var(--mola), background .2s;
}
.cta:hover  { transform: translateY(-2px); }
.cta:active { transform: translateY(0) scale(.97); transition-duration: .06s; }
motion — mola de verdade
const cta = document.querySelector('.cta');
if (!semMovimento) {
  cta.addEventListener('pointerdown', () =>
    animate(cta, { scale: .96 }, { type: 'spring', stiffness: 600, damping: 20 }));
  cta.addEventListener('pointerup', () =>
    animate(cta, { scale: 1 }, { type: 'spring', stiffness: 400, damping: 12 }));
}
Escolha: CSS, na quase totalidade dos casos. A mola real só se percebe num botão grande e num contexto de marca descontraída — e ela não aumenta conversão.

4 · Contador que anima ao aparecer

NovoJS
javascript puro
// <span class="num" data-num="300">0</span>
new IntersectionObserver((ents, obs) => {
  ents.forEach((e) => {
    if (!e.isIntersecting) return;
    obs.unobserve(e.target);
    const alvo = Number(e.target.dataset.num);
    if (semMovimento) { e.target.textContent = alvo; return; }
    const ini = performance.now(), dur = 1400;
    (function passo(t) {
      const p = Math.min((t - ini) / dur, 1);
      e.target.textContent = Math.round(alvo * (1 - Math.pow(1 - p, 3)));
      if (p < 1) requestAnimationFrame(passo);
    })(ini);
  });
}, { threshold: .4 }).observe(document.querySelector('.num'));
gsap — bem mais curto
const alvo = { v: 0 };
gsap.to(alvo, {
  v: 300, duration: 1.4, ease: 'power2.out',
  onUpdate: () => el.textContent = Math.round(alvo.v),
  scrollTrigger: { trigger: el, start: 'top 85%' }
});
Escolha: se já tem GSAP no projeto por outro motivo, use ele. Se não tem, não instale por causa de um contador — a versão em JavaScript puro resolve.
Lembre da regra da Parte 7 da trilha de IA: número em site de cliente só entra se for verdadeiro e confirmado por ele. Contador animado em cima de número inventado é publicidade enganosa com efeito bonito.

5 · Seção presa contando uma história

NovoJS

Este é o único efeito da lista que só o GSAP entrega pronto — e é o que faz aqueles sites de agência parecerem caros. Aplicado ao seu contexto: a seção de cronograma que se revela semana a semana enquanto a pessoa rola.

gsap + scrolltrigger
if (window.innerWidth > 900 && !semMovimento) {
  const tl = gsap.timeline({
    scrollTrigger: {
      trigger: '.cronograma',
      start: 'top top',
      end: '+=1800',
      pin: true,
      scrub: 1
    }
  });

  gsap.utils.toArray('.cronograma__semana').forEach((sem) => {
    tl.from(sem, { opacity: 0, x: -40, duration: 1 });
  });
}
Três cuidados antes de usar num cliente: desligado no celular, como acima; o conteúdo precisa existir no HTML e estar legível sem o efeito; e a pessoa tem que conseguir passar pela seção rápido se quiser — prender por 4000 pixels de rolagem irrita mais do que impressiona.

6 · Transição entre páginas

NovoCSS

Duas linhas de CSS que dão a um site multi-página a sensação de aplicativo. Nenhuma biblioteca faz melhor que o nativo aqui.

css
@view-transition { navigation: auto; }

::view-transition-old(root),
::view-transition-new(root) { animation-duration: .3s; }

/* a foto que "viaja" do card para a página do projeto */
.projeto__capa { view-transition-name: capa; }
Escolha: CSS, sem discussão. Onde o navegador não suportar, a navegação acontece do jeito normal — degradação perfeita.

A tabela de decisão

Novo
EfeitoUsePor quê
Reveal ao rolarCSSNativo faz melhor e de graça
Hover e focoCSSNunca precisou de biblioteca
Transição entre páginasCSSView Transitions resolve nativo
Lista de tamanho variávelMotion ou GSAPnth-child não dá conta
Mola respondendo a gestoMotionÉ a especialidade dele
Sequência longa e controlávelGSAPTimeline sem concorrente
Seção presa na rolagemGSAPScrollTrigger com pin
Número contandoJS puroVinte linhas; não vale uma dependência
Antes de instalar qualquer uma das duas, a pergunta permanece: o que exatamente o CSS não faz aqui? Em landing page de serviço, a resposta honesta costuma ser "nada" — e o peso, a dependência que envelhece e o risco de o CDN cair são custos reais cobrados de um cliente que nunca vai perceber a diferença.
PARTE 9

Laboratório: os efeitos rodando

Doze efeitos funcionando aqui dentro da página, cada um com o código que o faz funcionar. Passe o mouse, role, clique em rodar. Todos respeitam prefers-reduced-motion e nenhum depende de biblioteca para existir — só três usam Motion ou GSAP, e mesmo esses caem para a versão nativa se a biblioteca não carregar.

Como ler esta parte

Novo

Cada cartão tem três coisas: o efeito rodando, o selo da tecnologia que o move, e o código dobrado em ver código. O código é o de verdade — o mesmo que está rodando na sua tela agora, sem versão simplificada para a aula.

O botão rodar reinicia o efeito. Os que reagem a mouse ou rolagem não têm botão: o gesto é o disparo. E todos só começam a animar quando o cartão entra na tela — a mesma regra que você aplica em produção, porque animar o que ninguém está vendo é gastar processamento à toa e derrubar bateria de celular.

Sobre os três selos: CSS significa que não há uma linha de JavaScript envolvida. JS é JavaScript puro, sem dependência. Motion e GSAP são os únicos que carregam biblioteca — e, olhando os doze, é fácil ver o quanto disso o navegador já faz sozinho.

1 · Entradas — o primeiro segundo

Novo

O que acontece nos primeiros instantes da página. A regra aqui é contenção: se a entrada demora mais que um segundo no total, o visitante já sentiu que o site está lento, mesmo que esteja rápido.

Reforma completa
Projeto de interiores
Consultoria de ambiente
Entrada em cascata70ms entre itens
CSS
ver código
html + css
<div class="servicos">
  <div class="card" style="--i:0">Reforma completa</div>
  <div class="card" style="--i:1">Projeto de interiores</div>
  <div class="card" style="--i:2">Consultoria</div>
</div>

.card {
  opacity: 0;
  transform: translateY(14px);
  animation: sobe .5s cubic-bezier(.22,1,.36,1) forwards;
  animation-delay: calc(var(--i) * 70ms);
}
@keyframes sobe { to { opacity: 1; transform: none; } }
Arquitetura que cabe no seu prazo
Campo Grande · MS
Sequência de herotítulo, apoio, botão
GSAP
ver código
js
// o valor da timeline é o parâmetro de posição:
// '-=0.25' faz o próximo começar antes do anterior terminar
gsap.timeline()
  .from('.hero h1',  { y: 22, opacity: 0, duration: .5,  ease: 'power3.out' })
  .from('.hero p',   { y: 16, opacity: 0, duration: .45, ease: 'power3.out' }, '-=0.25')
  .from('.hero .cta',{ scale: .85, opacity: 0, duration: .45, ease: 'back.out(1.7)' }, '-=0.2');

// sem GSAP, a mesma coisa em nativo:
document.querySelectorAll('.hero > *').forEach((el, i) => {
  el.animate(
    [{ opacity: 0, transform: 'translateY(18px)' }, { opacity: 1, transform: 'none' }],
    { duration: 450, delay: i * 200, easing: 'cubic-bezier(.22,1,.36,1)', fill: 'both' }
  );
});
Vanessa CarvalhoArquiteta · CAU A12345-6
Esqueleto de carregamentoplaceholder → conteúdo
CSS
ver código
css
.linha {
  height: 11px; border-radius: 3px;
  background: linear-gradient(90deg, #e8e8e8 25%, #d5d5d5 37%, #e8e8e8 63%);
  background-size: 400% 100%;
  animation: brilho 1.3s linear infinite;
}
@keyframes brilho { to { background-position: -135% 0; } }

/* quando o conteúdo chega, some com o esqueleto */
.bloco.pronto .linha { display: none; }
Sobre o esqueleto: só vale quando o conteúdo realmente demora — dado vindo de API, imagem pesada. Em landing page estática ele é teatro: você está fingindo uma espera que não existe, e o visitante espera de verdade.

2 · Reação ao toque

Novo

Resposta ao gesto do visitante. Aqui a duração tem que ser curta — acima de 250ms o elemento parece preguiçoso, porque a mão já terminou o movimento e a interface ainda está indo.

Mola de verdadestiffness 260 · damping 12
Motion
ver código
js
<script src="https://cdn.jsdelivr.net/npm/motion@latest/dist/motion.js"></script>

const { animate } = Motion;

animate('.cta', { scale: [0.6, 1] }, {
  type: 'spring',
  stiffness: 260,   // força da mola: maior = mais rápido
  damping: 12,      // freio: menor = mais quique
  mass: 1           // peso: maior = mais lento e pesado
});
Reforma de apartamentoa partir de 45 dias
Card que convidapasse o mouse
CSS
ver código
css
.card {
  transition: transform .25s cubic-bezier(.22,1,.36,1),
              box-shadow .25s, border-color .25s;
}
.card:hover {
  transform: translateY(-4px);
  box-shadow: 0 12px 26px -14px rgba(0,0,0,.45);
  border-color: var(--accent);
}
/* nunca anime só no :hover — o teclado também navega */
.card:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
mexa o mouse
Inclinação 3D22° de amplitude
JS
ver código
css + js
.palco { perspective: 600px; }
.carta { transform-style: preserve-3d; transition: transform .12s ease-out; }

palco.addEventListener('pointermove', (e) => {
  const r = palco.getBoundingClientRect();
  const x = (e.clientX - r.left) / r.width  - .5;   // -0.5 a 0.5
  const y = (e.clientY - r.top)  / r.height - .5;
  carta.style.transform = `rotateY(${x * 22}deg) rotateX(${-y * 22}deg)`;
});
palco.addEventListener('pointerleave', () => carta.style.transform = '');
Cuidado com o tilt: ele não existe no celular, que é onde 70% do tráfego de cliente local está. Use como tempero em desktop, nunca como parte essencial da leitura.

3 · Ligado à rolagem

Novo

Os três abaixo têm caixas de rolagem próprias — role dentro delas para ver o efeito. É a categoria mais fácil de exagerar: cada elemento que aparece atrasa a leitura em um quarto de segundo, e uma página com trinta deles vira um site que se recusa a ser lido.

role para baixo ↓
Planta humanizada
Visita técnica
Acompanhamento de obra
fim
Revelar ao aparecerIntersectionObserver
JS
ver código
css + js
.revela { opacity: 0; transform: translateY(16px);
          transition: opacity .5s, transform .5s cubic-bezier(.22,1,.36,1); }
.revela.visivel { opacity: 1; transform: none; }

const obs = new IntersectionObserver((entradas) => {
  entradas.forEach(e => {
    // quem já passou por cima da tela numa rolagem rápida
    // nunca dispara o isIntersecting — e ficaria invisível para sempre
    const jaPassou = e.boundingClientRect.top < e.rootBounds.top;
    if (e.isIntersecting || jaPassou) {
      e.target.classList.add('visivel');
      obs.unobserve(e.target);   // anima uma vez só, e para de observar
    }
  });
}, { threshold: 0.35 });

document.querySelectorAll('.revela').forEach(el => obs.observe(el));
role esta caixa e olhe a barra acima ↑
Progresso de leiturascrollTop ÷ altura total
JS
ver código
css puro moderno
/* na página inteira, sem uma linha de JavaScript: */
.barra {
  position: fixed; top: 0; left: 0; height: 4px;
  background: var(--accent); transform-origin: 0 50%;
  animation: enche linear;
  animation-timeline: scroll(root block);
}
@keyframes enche { from { transform: scaleX(0); } to { transform: scaleX(1); } }

/* onde scroll() ainda não existe, a conta é esta: */
const max = el.scrollHeight - el.clientHeight;
fita.style.width = (el.scrollTop / max) * 100 + '%';
Reforma
Interiores
Comercial
Reforma
Interiores
Comercial
Faixa infinitapasse o mouse para pausar
CSS
ver código
css
/* o segredo: duplique a lista e ande exatamente 50% */
.fita { display: flex; gap: 10px; width: max-content;
        animation: corre 14s linear infinite; }
@keyframes corre { to { transform: translateX(-50%); } }

.marquee { overflow: hidden;
  mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent); }

.marquee:hover .fita { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) { .fita { animation: none; } }

4 · Detalhes que vendem

Novo

Os três que mais aparecem em página de serviço: o número que conta, a dúvida que abre e o botão que chama atenção sem gritar.

0
obras entregues
Número contandorAF · easeOutCubic
JS
ver código
js
function contar(el, alvo, dur = 1400) {
  let ini = null;
  function passo(t) {
    if (ini === null) ini = t;
    const p = Math.min((t - ini) / dur, 1);
    const e = 1 - Math.pow(1 - p, 3);      // easeOutCubic: rápido e desacelera
    el.textContent = Math.round(alvo * e);
    if (p < 1) requestAnimationFrame(passo);
  }
  requestAnimationFrame(passo);
}

/* e no CSS, para o número não tremer enquanto conta: */
.numero { font-variant-numeric: tabular-nums; }

Depende da metragem e do escopo. Projetos de interiores começam em R$ 8 mil.

De 45 a 90 dias, contando aprovação e execução.

Dúvida que abre0fr → 1fr
CSS
ver código
css
/* animar height: auto nunca funcionou. grid resolve: */
.corpo { display: grid; grid-template-rows: 0fr;
         transition: grid-template-rows .32s cubic-bezier(.4,0,.2,1); }
.item.aberto .corpo { grid-template-rows: 1fr; }
.corpo > div { overflow: hidden; }   /* esta linha é obrigatória */

/* o JS é uma linha: */
cab.addEventListener('click', () => item.classList.toggle('aberto'));
Pulso no CTAanel a cada 2,4s
CSS
ver código
css
.cta { position: relative; }
.cta::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 55%, transparent);
  animation: pulso 2.4s cubic-bezier(.22,1,.36,1) infinite;
}
@keyframes pulso { to { box-shadow: 0 0 0 16px transparent; } }

/* o ::after não captura clique, então o botão continua clicável */
@media (prefers-reduced-motion: reduce) { .cta::after { animation: none; } }
Regra do pulso: um por página, no CTA principal, e só ele. Dois elementos pulsando ao mesmo tempo não chamam o dobro da atenção — chamam zero, porque o olho para de saber para onde ir.