Referência da biblioteca Motion escrita em português, organizada por problema e não por ordem alfabética. Cada função traz o quadro de opções, o exemplo no contexto de página de cliente, e — a parte que documentação nenhuma dá — quando o CSS resolve melhor e você não deveria estar aqui.
Revisão já conversamos sobre issoNovo ainda não vimos8 partes · 46 tópicos
Mostrar
Nenhum tópico com esse filtro.
PARTE 1
Como o Motion pensa
Antes dos comandos, o modelo. O Motion não é "CSS com JavaScript": ele tem um jeito próprio de tratar tempo, valor e interrupção, e entender isso faz a diferença entre usar a biblioteca e brigar com ela.
De onde ele veio, e por que o nome mudou
Novo
O que hoje se chama Motion nasceu como Framer Motion, a biblioteca de animação do Framer para React. Depois se separou da empresa e virou projeto independente, com uma diferença que importa para você: além da versão React, passou a existir uma versão em JavaScript puro, que funciona em qualquer página — inclusive num HTML único autocontido.
Isso muda a conversa. Antes, usar Motion significava adotar React; hoje, significa uma tag <script>. É o que o traz para dentro do seu tipo de entrega.
Consequência prática na hora de pesquisar: muito material antigo na internet fala de "Framer Motion" e mostra sintaxe de React (<motion.div animate={...}>). Se você está em HTML puro, essa sintaxe não se aplica — procure pela API de JavaScript, que é baseada em funções: animate, scroll, inView, stagger.
As versões — e qual é a sua
Novo
Versão
Como se escreve
Para você
JavaScript
Funções: animate(el, {...}, {...})
É esta. Funciona em HTML puro, sem build
React
Componentes: <motion.div animate={...}>
Só se um dia o projeto for React
Vue
Componentes, sintaxe equivalente à do React
Mesma coisa, outro framework
As três compartilham o mesmo motor. As opções de tempo, as curvas e os nomes de mola são idênticos — o que muda é a embalagem. Aprender a versão JavaScript e depois migrar para React custa pouco, porque o vocabulário é o mesmo.
Esta trilha inteira usa a versão JavaScript. A Parte 8 mostra o equivalente em React de cada coisa, para o dia em que fizer sentido — e para você reconhecer o que vê na internet.
<script type="module">
import { animate, stagger } from "https://cdn.jsdelivr.net/npm/motion@latest/+esm";
</script>
3 · com build
npm install motion
# import { animate } from "motion"
Caminho
Use quando
Cuidado
Global (CDN)
HTML único, entrega de cliente
Depende do CDN estar no ar
Módulo ES
Quer só uma ou duas funções
Não roda em file://, precisa de servidor
npm
Projeto com Vite, Astro, React
Traz node_modules junto
Fixe a versão em entrega de cliente.motion@latest é ótimo para estudar e péssimo para um site que vai ficar dois anos no ar: uma mudança grande na biblioteca chega sozinha e quebra a página sem ninguém ter tocado em nada. Troque por uma versão explícita, como motion@13, e atualize quando você quiser.
O que roda por baixo
Novo
O Motion tenta entregar a animação para o próprio navegador executar, usando a API de animações nativa (a mesma do element.animate()). Quando consegue, a animação roda fora da linha principal do JavaScript — ou seja, continua fluida mesmo se o seu código estiver ocupado.
Quando não consegue — porque a propriedade não é acelerável, ou porque é uma mola que depende de cálculo quadro a quadro — ele cai para um motor próprio em JavaScript.
O que você anima
Custo
x, y, scale, rotate, opacity
Barato — o navegador resolve na placa de vídeo
filter, clipPath, cor de fundo
Médio — redesenha, mas não recalcula layout
width, height, top, margin
Caro — força recálculo de layout a cada quadro
Esta tabela vale para qualquer técnica de animação, não só para o Motion — é a mesma regra da trilha Movimento. Biblioteca nenhuma torna barato animar width; o que ela faz é tornar conveniente animar a coisa errada.
O que ele faz que o CSS não faz
Novo
A pergunta que decide se vale instalar. São cinco coisas, e só elas:
Capacidade
Por que o CSS não dá conta
Mola de verdade
CSS só tem curvas de duração fixa. Mola não tem duração — ela termina quando a energia acaba.
Cascata em lista de tamanho desconhecido
nth-child exige você escrever um delay por item. Com 40 itens vindos de dados, não dá.
Interrupção que preserva velocidade
Em CSS, interromper uma transição reinicia do estado atual com velocidade zero. Fica travado.
Sequência longa e controlável
Encadear seis passos com sobreposição em CSS vira uma tabela de delays impossível de manter.
Ler o progresso e usar em outra coisa
CSS não te entrega um número de 0 a 1 para você fazer conta.
E o que não está na lista: revelar ao rolar, hover, transição entre páginas, pulso, cascata de três itens fixos. Tudo isso o CSS moderno faz melhor, mais leve e sem dependência. Se o seu projeto só precisa dessas, feche esta trilha e volte para a de Movimento — a decisão certa é não instalar nada.
PARTE 2
animate() — a função central
Noventa por cento do que você vai fazer passa por aqui. Vale entender bem as quatro formas de chamá-la, porque a terceira e a quarta são as que resolvem problema que o CSS não resolve.
As quatro formas de chamar
NovoJS
js
// 1 · um elemento (ou seletor, ou lista de elementos)
animate('.cta', { scale: [0.9, 1] }, { duration: 0.4 });
// 2 · de um estado para outro, explicitamente
animate('.cta', { opacity: [0, 1] }); // array = [de, para]
animate('.cta', { opacity: 1 }); // sem array = do atual até 1
// 3 · um número solto, sem elemento nenhum
animate(0, 248, {
duration: 1.4,
onUpdate: v => contador.textContent = Math.round(v)
});
// 4 · uma sequência de passos (ver Parte 4)
animate([
['.titulo', { y: [20, 0], opacity: [0, 1] }, { duration: 0.5 }],
['.cta', { scale: [0.9, 1] }, { at: '-0.2' }]
]);
A terceira forma é a mais subestimada. Ela anima um número, não um elemento — e te entrega o valor a cada quadro no onUpdate. É com ela que se faz contador, barra de progresso com texto, contagem de vagas, valor de orçamento subindo. Nada disso tem a ver com CSS.
O que dá para animar
NovoJS
A diferença mais prática em relação ao CSS: cada transformação é uma propriedade independente. Em CSS, transform é uma string só — animar x e rotate em tempos diferentes exige ginástica. Aqui não.
opacity, qualquer propriedade CSS pelo nome, variáveis CSS
Unidade omitida em x e y significa pixels. Porcentagem também vale, e é relativa ao próprio elemento: x: '100%' move o elemento pela própria largura — ótimo para carrossel e menu deslizante.
As opções de tempo
NovoJS
Opção
Padrão
O que faz
duration
0.3 (tween)
Segundos, não milissegundos — diferente do CSS
delay
0
Espera antes de começar. Aceita stagger().
repeat
0
Quantas vezes repetir. Infinity para sempre.
repeatType
"loop"
loop recomeça, reverse volta, mirror volta com a curva espelhada
repeatDelay
0
Pausa entre repetições
times
—
Onde cada keyframe cai, de 0 a 1
ease
"easeOut"
A curva — ver Parte 3
js
// times: por padrão os keyframes são igualmente espaçados.
// aqui o pico acontece em 30% do tempo, e os 70% restantes são a descida:
animate('.selo', { scale: [1, 1.25, 1] }, {
duration: 0.8,
times: [0, 0.3, 1],
ease: 'easeOut'
});
Segundos, não milissegundos. É o erro número um de quem vem do CSS: escrever duration: 300 achando que são 300ms. São cinco minutos. Se a animação "não está acontecendo", confira isso antes de qualquer outra coisa.
Tempo diferente para cada propriedade
NovoJS
Recurso que o CSS não tem e que é o que dá "acabamento" à animação: dentro do objeto de opções, cada propriedade pode ter o próprio tempo e a própria curva.
js
animate('.card-servico',
{ opacity: [0, 1], y: [24, 0], scale: [0.96, 1] },
{
duration: 0.5, // padrão para todas
opacity: { duration: 0.25 }, // a opacidade entra mais rápido
y: { type: 'spring', bounce: 0.2 }, // o deslocamento tem mola
scale: { duration: 0.6, ease: 'backOut' } // e a escala passa um pouco
}
);
Por que isso importa: opacidade e movimento devem terminar em momentos diferentes. Quando os dois acabam juntos, o olho percebe o elemento "chegando e parando"; quando a opacidade termina antes, o elemento parece já estar lá e só se acomodando. É uma diferença de 200 milissegundos que separa animação amadora de profissional.
O objeto de controle
NovoJS
animate() devolve um objeto com o qual você comanda a animação depois de iniciada.
js
const a = animate('.faixa', { x: '-50%' }, { duration: 14, repeat: Infinity, ease: 'linear' });
a.pause(); // congela
a.play(); // segue
a.cancel(); // desfaz e volta ao estado original
a.complete(); // pula direto para o fim
a.stop(); // para onde está, sem desfazer
a.time = 2; // pula para o segundo 2 (também lê)
a.speed = 0.5; // câmera lenta; negativo roda de trás para frente
a.duration; // duração final, já calculada (útil com mola)
await a; // dá para esperar o fim — o objeto é "thenable"
a.then(() => console.log('acabou'));
a.duration é especialmente útil com mola: como a mola não tem duração declarada, esse é o jeito de descobrir quanto ela realmente vai levar — por exemplo, para disparar a próxima coisa no momento certo.
Interromper sem quebrar
NovoJS
Este é o motivo técnico mais forte para usar uma biblioteca de animação em interface que responde a gesto. Quando você chama animate() de novo no mesmo elemento, a animação anterior é substituída preservando a velocidade atual — o elemento continua o movimento em vez de dar um solavanco.
js
const menu = document.querySelector('.menu');
function abrir() { animate(menu, { x: 0 }, { type: 'spring', bounce: 0.15 }); }
function fechar() { animate(menu, { x: '-100%' }, { type: 'spring', bounce: 0.15 }); }
// abrir e fechar rápido, várias vezes: o menu muda de direção
// sem parar no meio do caminho. Em CSS, cada troca reinicia do zero.
Em CSS isso não existe: interromper uma transition faz o elemento recomeçar do ponto atual com velocidade zero. Em movimento rápido, o olho vê a parada. É invisível numa página estática e muito visível num menu que o usuário abre e fecha com o polegar.
Respeitar quem pediu menos movimento
NovoJS
Biblioteca de animação não lê prefers-reduced-motion sozinha. Em CSS você resolve com uma media query; aqui, é sua responsabilidade — e é obrigação, não capricho.
js
const calmo = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
// versão 1: não anima, só aplica o estado final
if (calmo) {
document.querySelectorAll('.revela').forEach(el => el.style.opacity = 1);
} else {
animate('.revela', { opacity: [0, 1], y: [20, 0] }, { delay: stagger(0.06) });
}
// versão 2: anima, mas sem deslocamento e bem mais curto
animate('.revela',
calmo ? { opacity: [0, 1] } : { opacity: [0, 1], y: [20, 0] },
{ duration: calmo ? 0.01 : 0.5 }
);
A versão 2 costuma ser melhor: quem ativou a preferência não quer nenhum movimento, mas ainda precisa perceber que algo mudou. Opacidade curta cumpre isso sem provocar enjoo — que é o sintoma real de quem liga essa opção.
PARTE 3
Curvas: tween, spring e inertia
A curva é o que faz a animação parecer barata ou cara — mais do que a duração, mais do que a distância. O Motion tem três motores, e escolher o certo é metade do resultado.
tween — duração fixa, curva declarada
NovoJS
É o motor padrão e o mais próximo do que você já conhece do CSS: você diz quanto tempo dura e qual o formato da aceleração.
js
animate('.card', { y: [20, 0] }, { type: 'tween', duration: 0.5, ease: 'easeOut' });
// type: 'tween' é o padrão — pode omitir
As curvas com nome, e onde cada uma serve:
Nome
Sensação
Use em
linear
Mecânica, sem aceleração
Rotação contínua, faixa infinita, barra ligada à rolagem
easeOut
Entra rápido e desacelera
O padrão para quase tudo que aparece. É como as coisas chegam no mundo real
easeIn
Começa devagar e acelera
Só para o que sai da tela
easeInOut
Suave nas duas pontas
Movimento longo, de um ponto a outro
backOut
Passa do alvo e volta
Botão, selo, ícone — dá vida sem custar peso
backIn
Recua antes de ir
Saída com intenção
circOut
Desaceleração bem acentuada
Quando easeOut parece mole demais
anticipate
Recua, depois dispara
Um elemento só, uma vez. Cansa rápido.
Cada uma dessas tem as variantes In, Out e InOut. Na dúvida, easeOut: é o que o olho lê como natural, porque objeto que chega no mundo físico desacelera até parar.
Curva própria — bezier e steps
NovoJS
js
// os quatro números do cubic-bezier, iguais aos do CSS
animate('.card', { y: [20, 0] }, { ease: [0.22, 1, 0.36, 1] });
// curva por keyframe: uma para cada trecho
animate('.selo', { scale: [1, 1.2, 1] }, { ease: ['easeOut', 'easeIn'] });
// movimento em degraus, sem suavização
animate('.digito', { y: -100 }, { ease: steps(5) });
// e a sua própria função: recebe 0→1, devolve 0→1
animate('.x', { x: 100 }, { ease: t => t * t });
A curva que eu usaria como padrão do seu design system:[0.22, 1, 0.36, 1]. Ela sai forte e freia bastante no fim — é o que dá a sensação de "caro" sem exagero. Declare uma vez como token e use no site inteiro; movimento coerente vale mais que movimento variado.
spring — o jeito novo, com dois números
NovoJS
Mola não tem duração: ela termina quando a energia acaba. Isso sempre foi difícil de ajustar, porque os parâmetros físicos não têm relação intuitiva com o resultado. A forma moderna resolve com dois números que você entende de cara:
js
animate('.cta', { scale: [0.9, 1] }, {
type: 'spring',
bounce: 0.25, // 0 = sem quique; 1 = muito quique
visualDuration: 0.4 // quanto tempo até "parecer" que chegou
});
bounce
Resultado
Onde usar
0
Chega e para, sem passar do ponto
Menu, gaveta, painel
0.1 – 0.2
Um toque de vida, quase imperceptível
Card, seção entrando
0.25 – 0.4
Quique claro e simpático
Botão, selo, ícone
0.5+
Brincalhão, chama atenção
Confete, mascote. Em site de serviço, raramente.
visualDuration não é a duração total. É o tempo até o elemento parecer ter chegado ao destino — o quique residual continua depois. É por isso que ele é útil: você ajusta pela percepção, não pelo relógio.
spring — o jeito físico, com três números
NovoJS
O modelo clássico, que você vai encontrar em todo material antigo e em outras bibliotecas. Continua funcionando e dá controle mais fino.
js
animate('.cta', { scale: [0.9, 1] }, {
type: 'spring',
stiffness: 260, // força da mola: maior = mais rápido e agressivo
damping: 20, // freio: menor = mais quique; maior = para antes
mass: 1, // peso: maior = mais lento e pesado
velocity: 0, // velocidade inicial
restDelta: 0.01 // o quão perto do alvo conta como "chegou"
});
Combinação
Sensação
stiffness: 120, damping: 20
Lenta e suave — gaveta grande
stiffness: 260, damping: 20
Equilibrada — serve para quase tudo
stiffness: 400, damping: 30
Rápida e firme — resposta a toque
stiffness: 260, damping: 10
Quicando bastante
stiffness: 700, damping: 40
Quase instantânea, com um resquício de física
Qual dos dois usar: comece por bounce e visualDuration — você acerta em duas tentativas. Vá para o trio físico só quando precisar de algo específico que os dois números não alcançam, ou quando estiver traduzindo um valor de outra biblioteca.
inertia — quando o dedo solta
NovoJS
O terceiro motor, e o mais específico: ele pega a velocidade que o elemento já tinha e a desacelera, como um objeto deslizando. É o que existe por trás da rolagem com inércia do celular.
js
animate('.carrossel', { x: posicaoFinal }, {
type: 'inertia',
velocity: velocidadeDoGesto, // vem do evento de ponteiro
power: 0.8, // quanto a velocidade projeta distância
timeConstant: 700, // quão rápido desacelera
min: -1200, max: 0, // limites — encosta e para
bounceStiffness: 500 // e o que acontece ao bater no limite
});
Na prática, você quase não chama isso direto. Ele aparece quando você usa o recurso de arrastar — em React, é o que o drag usa ao soltar. Para site de serviço local, é o motor que você mais vai ler sobre e menos vai usar. Está aqui para o mapa ficar completo.
Qual motor para qual situação
Novo
Situação
Motor
Ajuste inicial
Elemento entrando na tela
tween
duration: 0.5, ease: 'easeOut'
Lista em cascata
tween
duration: 0.45 + stagger(0.06)
Botão respondendo a clique
spring
bounce: 0.3, visualDuration: 0.3
Menu ou gaveta abrindo
spring
bounce: 0, visualDuration: 0.35
Elemento seguindo o ponteiro
spring
stiffness: 400, damping: 30
Faixa infinita
tween
ease: 'linear', repeat: Infinity
Ligado à rolagem
tween
ease: 'linear' — a rolagem já é a curva
Depois de arrastar
inertia
Com a velocidade do gesto
Duas regras que cobrem o resto: mola para o que responde a gesto humano, tween para o que acontece sozinho. E linear nunca em entrada — movimento sem aceleração parece máquina, e é o sinal mais rápido de animação malfeita.
PARTE 4
stagger e sequências
Duas ferramentas para coordenar várias coisas no tempo. A primeira resolve "a mesma animação, defasada"; a segunda resolve "coisas diferentes, em ordem". São os dois casos em que o CSS trava de verdade.
stagger — a cascata
NovoJS
stagger() não é uma animação: é um gerador de delay. Você o passa no lugar do delay, e ele calcula um valor diferente para cada elemento da lista.
js
const { animate, stagger } = Motion;
animate('.servico',
{ opacity: [0, 1], y: [20, 0] },
{ duration: 0.45, delay: stagger(0.06) }
);
// item 1 em 0s, item 2 em 0.06s, item 3 em 0.12s… e assim por diante,
// independente de serem 3 ou 300 itens
Opção
Padrão
O que faz
1º argumento
—
Segundos entre um item e o seguinte
startDelay
0
Espera antes do primeiro item
from
"first"
De onde a onda parte: "first", "center", "last", ou um índice
ease
"linear"
Curva da distribuição dos delays, não da animação
js
// a onda sai do meio da grade para as bordas
animate('.foto', { opacity: [0, 1], scale: [0.92, 1] }, {
duration: 0.5,
delay: stagger(0.05, { from: 'center', startDelay: 0.2 })
});
// delays que vão ficando mais juntos: começa espaçado, termina rápido
animate('.linha', { x: [-20, 0], opacity: [0, 1] }, {
delay: stagger(0.08, { ease: 'easeOut' })
});
O número certo: entre 0.04 e 0.08 segundos. Acima de 0.1, com dez itens, a última coisa aparece um segundo depois da primeira — e o visitante já rolou a página. Cascata existe para dar ritmo, não para atrasar leitura: se a lista tem mais de doze itens, reduza para 0.03 ou anime só os primeiros.
Quando o CSS resolve o mesmo
RevisãoCSS
Honestidade antes da ferramenta: se a lista tem tamanho fixo e você escreve o HTML, o CSS faz a mesma cascata sem uma linha de JavaScript.
Precisa de from: 'center' ou ordem que não seja a do DOM
Vai animar uma vez, ao aparecer
Precisa reanimar, inverter ou interromper
Nas suas landing pages, quase sempre a coluna da esquerda vence. A coluna da direita aparece em catálogo, listagem filtrável e galeria — que é exatamente quando o projeto já deixou de ser uma landing page.
Sequência — coisas diferentes, em ordem
NovoJS
Quando os elementos são diferentes e cada um tem a própria animação, você passa um array de passos. Cada passo é [alvo, propriedades, opções].
A sobreposição é o segredo da hero. Sem at, cada elemento espera o anterior acabar e a entrada leva dois segundos — parece lenta. Com -0.2 entre eles, o conjunto entra em menos de um segundo e ainda se lê como uma sequência, não como um bloco.
Controlar a sequência inteira
NovoJS
A sequência devolve o mesmo objeto de controle de uma animação simples — e ele comanda o conjunto todo.
js
const entrada = animate([
['.hero h1', { y: [24, 0], opacity: [0, 1] }],
['.hero .cta', { scale: [0.9, 1] }, { at: '-0.2' }]
]);
entrada.speed = 0.25; // câmera lenta, para você ajustar olhando
entrada.time = 0.4; // pula para um instante específico
await entrada; // espera tudo terminar
entrada.cancel(); // desfaz o conjunto
Truque de ajuste: enquanto está calibrando uma sequência, rode com speed = 0.2. Em velocidade normal é impossível enxergar se a sobreposição está boa; a quatro vezes mais lento, os erros de tempo ficam óbvios. Depois volte para 1 e confira.
Sequência do Motion × timeline do GSAP
Novo
Motion — sequência
GSAP — timeline
Formato
Um array, declarado de uma vez
Objeto encadeado, construído passo a passo
Sobreposição
at: '-0.2'
'-=0.2'
Simultâneo
at: '<'
'<'
Montar em partes
Difícil — o array é fechado
Natural: guarda a timeline e vai somando
Rótulos nomeados
Não tem
Tem — addLabel e salto por nome
Aninhar sequências
Não
Timeline dentro de timeline
Peso
Bem menor
Maior, e cresce com plugin
A leitura prática: para uma hero de quatro elementos, a sequência do Motion é mais limpa e pesa menos. Para uma narrativa longa de rolagem, com dez momentos que você monta em partes e quer poder pular por nome, a timeline do GSAP não tem concorrente — e é aí que instalar GSAP se paga.
PARTE 5
scroll() — ligar movimento à rolagem
A função que dá acesso ao progresso da rolagem como um número de 0 a 1. É o recurso mais poderoso da biblioteca e o mais fácil de exagerar — vale ler a última parte deste bloco antes de sair usando.
As duas formas
NovoJS
js
const { scroll, animate } = Motion;
// 1 · função de retorno: você recebe o progresso e faz o que quiser
scroll(progresso => {
barra.style.transform = `scaleX(${progresso})`;
});
// 2 · animação amarrada à rolagem: em vez de rodar no tempo,
// ela avança conforme a página desce
scroll(
animate('.barra', { scaleX: [0, 1] }, { ease: 'linear' })
);
// as duas devolvem uma função para desligar
const parar = scroll(p => { /* … */ });
parar();
Use ease: 'linear' em animação amarrada à rolagem. A curva já é o gesto da pessoa: se você acrescentar uma curva por cima, o movimento fica descompassado do dedo dela, e a sensação é de atraso.
As opções
NovoJS
Opção
Padrão
O que faz
container
window
Qual área rola. Aponte para um elemento se o scroll for interno.
axis
"y"
"x" para carrossel horizontal
target
toda a área
Medir a passagem de um elemento, não da página inteira
trackContentSize resolve um bug silencioso: se a página tem imagem sem width/height, ou conteúdo que abre, a altura muda depois do cálculo e o progresso fica errado. Ligue quando houver acordeão, galeria ou conteúdo carregado depois.
offset — a gramática das duas palavras
NovoJS
A parte que confunde todo mundo na primeira vez. Cada entrada do array tem duas palavras: a primeira é um ponto do elemento, a segunda é um ponto da tela. O progresso vale 0 quando esses dois pontos se encontram.
js
offset: ['start end', 'end start']
// │ │ │ └── topo da tela
// │ │ └── fim do elemento
// │ └── base da tela
// └── começo do elemento
// leitura: "vale 0 quando o topo do elemento encosta na base da tela;
// vale 1 quando o fim do elemento sai pelo topo da tela"
offset
Cobre
Use para
['start end', 'end start']
Toda a travessia pela tela
Parallax, fade de entrada e saída
['start end', 'start start']
Da entrada até encostar no topo
Revelar enquanto sobe
['start start', 'end end']
Enquanto o elemento é maior que a tela
Progresso de leitura de um artigo
['center end', 'center start']
Centrado no elemento
Efeito que tem pico no meio
Além das palavras, valem número de 0 a 1, pixels e unidades de tela: offset: ['start 80vh', 'end 20vh'].
Como acertar sem adivinhar: imprima o progresso no console enquanto rola — scroll(p => console.log(p.toFixed(2)), { target, offset }). Em dez segundos você vê se o 0 e o 1 caem onde você queria. É mais rápido que raciocinar sobre as palavras.
O segundo argumento: velocidade e distância
NovoJS
A função de retorno recebe um segundo parâmetro com dados crus da rolagem — inclusive a velocidade, que abre efeitos que o progresso sozinho não dá.
js
scroll((progresso, info) => {
info.y.current; // pixels já rolados
info.y.progress; // o mesmo 0→1 do primeiro argumento
info.y.velocity; // pixels por segundo; negativo = subindo
info.y.scrollLength; // quanto há para rolar no total
});
uso real: esconder o cabeçalho ao descer, mostrar ao subir
Repare no controle escondido: sem ele, você dispararia uma animação nova a cada quadro de rolagem. É o erro de desempenho mais comum em efeito ligado a scroll — e o sintoma é o celular esquentando.
Efeito ligado à rolagem é o que mais impressiona em portfólio e o que mais atrapalha em página de venda. Quatro motivos concretos:
Problema
Por quê
Atrasa a leitura
O visitante rola para ler, não para assistir. Conteúdo que só aparece depois do efeito é conteúdo escondido.
Quebra a busca dentro da página
Ctrl+F encontra texto com opacity: 0 e leva a pessoa a uma área aparentemente vazia
Enjoa de verdade
Parallax é a principal queixa de quem tem sensibilidade vestibular
Custa em celular fraco
Cada quadro de rolagem roda seu código, e a maioria do tráfego local é Android de entrada
O limite que eu recomendo para landing page de serviço: um efeito de rolagem por página, no máximo — geralmente a barra de progresso ou um parallax de 40 a 60 pixels na hero. Tudo além disso, desligue no celular e sob prefers-reduced-motion. A página tem que vender, não impressionar outro desenvolvedor.
PARTE 6
inView e gestos
As funções que reagem ao visitante: quando um elemento entra na tela, quando o ponteiro passa, quando o dedo pressiona. Todas devolvem uma função de limpeza — e usá-la é o que separa código que funciona de código que vaza memória.
Aumenta ou reduz a área de disparo. "0px 0px -15% 0px" dispara um pouco antes de entrar de fato.
amount
"some"
"some", "all", ou de 0 a 1 — quanto do elemento precisa aparecer
E o detalhe mais útil: se a sua função devolver outra função, ela é chamada quando o elemento sai da tela.
js
const parar = inView('.video-secao', (el) => {
const v = el.querySelector('video');
v.play();
return () => v.pause(); // roda ao sair da tela
}, { amount: 0.5 });
parar(); // desliga o observador de vez
Anime uma vez, não toda vez. Por padrão o retorno de saída faz o efeito repetir a cada passagem, e conteúdo que pisca a cada rolagem é irritante. Se for entrada de seção, marque o elemento e não reanime — ou use o CSS, que com animation-timeline: view() resolve isso sem JavaScript nenhum.
hover e press
NovoJS
À primeira vista parecem só um atalho para addEventListener. A diferença está nos casos de borda que eles já resolvem.
hover ignora toque — no celular, um toque não dispara "hover grudado"
press encerra corretamente se o dedo sair do elemento antes de soltar
Os dois aceitam seletor e valem para elementos que aparecem depois
Os dois devolvem função de limpeza, e a de "saída" vem do seu retorno
Ainda assim, pense duas vezes. Hover e press em CSS são uma linha, funcionam sem JavaScript e nunca falham. Só vale trazer para cá quando a resposta precisa de mola ou precisa interromper outra animação em curso — que é exatamente o que o CSS não faz. Para um card que levanta 4 pixels, continue no CSS.
Limpeza — o que ninguém ensina e todo mundo esquece
NovoJS
Em página estática, esquecer de limpar não dói: a página recarrega e tudo some. O problema aparece em site com troca de conteúdo sem recarregar — e no seu caso, mais cedo do que parece, porque a View Transition do CSS não recarrega o JavaScript.
js
// guarde TODAS as funções de limpeza
const limpezas = [];
limpezas.push(inView('.secao', aparecer));
limpezas.push(hover('.card', levantar));
limpezas.push(scroll(atualizarBarra));
function desmontar() {
limpezas.forEach(f => f());
limpezas.length = 0;
}
O sintoma de não limpar é característico: a página vai ficando lenta conforme o visitante navega, e o celular esquenta. São observadores antigos ainda rodando sobre elementos que nem existem mais.
Arrastar
NovoJS
Aqui a versão JavaScript e a versão React se separam bastante. Em React, arrastar é uma propriedade — drag, dragConstraints, dragElastic, dragMomentum, e ao soltar o motor de inércia entra sozinho. Em JavaScript puro, você monta o gesto na mão e usa o Motion para a parte animada.
Arraste e solte — ele volta com molaresistência depois de 120px
js
/* quanto mais longe, menos anda: resistência em vez de parede */
function comResistencia(d, limite) {
var a = Math.abs(d);
return a <= limite ? d : Math.sign(d) * (limite + (a - limite) * 0.3);
}
/* ao soltar, volta com uma curva que passa do ponto */
cartao.animate(
[{ transform: atual }, { transform: 'none' }],
{ duration: 500, easing: 'cubic-bezier(.34, 1.56, .64, 1)' }
);
Antes de construir isso: para carrossel de fotos, overflow-x: auto com scroll-snap-type: x mandatory resolve em quatro linhas de CSS, funciona com teclado, funciona com leitor de tela e tem inércia nativa do sistema. Gesto de arrastar feito à mão quase sempre fica pior que o nativo — e custa um dia.
A tabela honesta desta parte
Novo
Você quer
Use
Por quê
Revelar seção ao rolar
CSS animation-timeline: view()
Nativo, zero JavaScript, degrada sozinho
Revelar com cascata de N itens
inView + stagger
É o caso que o CSS não cobre
Card levantando no hover
CSS
Uma linha, sem dependência
Botão com retorno de mola
press + spring
Mola o CSS não faz
Vídeo que toca ao aparecer
inView com retorno
Precisa de JavaScript de qualquer jeito
Carrossel arrastável
CSS scroll-snap
Nativo é melhor que qualquer reimplementação
Quatro das seis linhas apontam para o CSS. Isso não é argumento contra o Motion — é o argumento a favor de usá-lo só nas duas linhas em que ele é insubstituível. Biblioteca boa é a que você usa pouco e no lugar certo.
PARTE 7
SVG, 3D e valores soltos
Três territórios que não são o dia a dia, mas que resolvem pedidos específicos: o traço que se desenha sozinho, a profundidade, e a animação de um número que não tem elemento nenhum por trás.
O traço que se desenha
NovoJS
O efeito mais reconhecível em SVG: uma linha, um ícone ou uma assinatura que aparece como se estivesse sendo desenhada. O truque real é antigo — stroke-dasharray e stroke-dashoffset —, e o Motion embrulha isso em nomes que você entende.
js
animate('.assinatura path',
{ pathLength: [0, 1] },
{ duration: 1.6, ease: 'easeInOut' }
);
// pathLength → quanto do traço está visível, de 0 a 1
// pathOffset → desloca o trecho visível ao longo da linha
// pathSpacing → o espaço entre os pedaços, quando tracejado
Onde isso ganha dinheiro: logotipo desenhando na hero, um traço sublinhando a palavra-chave da chamada, o contorno de um ícone se formando ao entrar na tela. É barato — um SVG pesa alguns KB — e passa impressão de acabamento caro. Use uma vez por página.
O que não dá para fazer com SVG aqui
Novo
Morfar uma forma em outra não é suportado. Transformar um círculo num raio, uma seta num X, o contorno de um estado no contorno de outro — isso exige interpolar o próprio comando do caminho, e é um problema difícil: os dois caminhos precisam ter o mesmo número de pontos, na mesma ordem.
Você quer
Caminho
Traço se desenhando
Motion, com pathLength
Mover, girar, escalar um SVG
Motion, normalmente — SVG aceita transform
Animar cor, opacidade, espessura do traço
Motion, pelo nome da propriedade
Uma forma virando outra
Plugin de morph do GSAP, ou trocar de ícone com fade
Ícone de menu virando X
CSS — são três barras girando, não um morph
A última linha vale registrar: o "hambúrguer que vira X" parece morph e não é. São três retângulos: o do meio some, os outros dois giram 45 graus em direções opostas e se centralizam. Doze linhas de CSS, sem biblioteca.
Profundidade
NovoJS
js + css
/* a perspectiva vive no PAI — sem ela, o 3D achata e nada acontece */
.palco { perspective: 900px; }
.carta { transform-style: preserve-3d; }
animate('.carta', { rotateY: 180 }, { type: 'spring', bounce: 0.2 });
animate('.carta', {
rotateX: -12,
rotateY: 18,
z: 40, // aproxima do observador
transformPerspective: 800 // perspectiva no próprio elemento
}, { duration: 0.4 });
Valor de perspective
Efeito
300–600px
Dramático, quase caricato
800–1200px
Natural — é a faixa que você quer
2000px+
Sutil a ponto de quase não existir
Dois erros clássicos: esquecer a perspective no pai (e concluir que "não funciona"), e esquecer backface-visibility: hidden na carta que vira — sem isso, o verso aparece espelhado no meio do giro.
Animar um valor que não é elemento
NovoJS
O recurso que mais rende em site de serviço, e o menos lembrado. Você anima um número e decide o que fazer com ele a cada quadro.
js
// número simples
animate(0, 248, {
duration: 1.4, ease: 'easeOut',
onUpdate: v => el.textContent = Math.round(v)
});
// valor com formato brasileiro
animate(0, 18500, {
duration: 1.2,
onUpdate: v => el.textContent = v.toLocaleString('pt-BR', {
style: 'currency', currency: 'BRL', maximumFractionDigits: 0
})
});
// entre duas cores, sem elemento
animate('#305546', '#B8461F', {
duration: 1,
onUpdate: cor => document.body.style.setProperty('--destaque', cor)
});
// um objeto inteiro de uma vez
const estado = { x: 0, opacidade: 0 };
animate(estado, { x: 100, opacidade: 1 }, {
duration: 0.6,
onUpdate: () => desenharNoCanvas(estado)
});
O uso que eu mais recomendo: contador de obras entregues, de anos de atuação, de clientes atendidos. É o número que o cliente quer destacar, ele sobe em um segundo e meio, e custa dez linhas. E como é só texto, funciona com leitor de tela se você deixar o valor final também escrito no HTML.
Animar variáveis CSS
NovoCSS
Uma ponte elegante entre o JavaScript e o seu design system: em vez de animar cada elemento, você anima um token e o CSS distribui.
/* declare o tipo — sem isso o navegador trata como texto e não interpola */
@property --intensidade {
syntax: '<number>';
inherits: true;
initial-value: 0;
}
.secao { background: rgb(48 85 70 / calc(var(--intensidade) * 0.12)); }
.secao .cta { transform: scale(calc(1 + var(--intensidade) * 0.04)); }
Um valor animado, vários elementos reagindo — e a lógica de aparência continua toda no CSS, onde ela deve estar. É o padrão que mantém o JavaScript magro à medida que o efeito cresce.
PARTE 8
O equivalente em React
Você não usa React hoje. Esta parte existe por dois motivos: para você entender o material que encontrar na internet, que em maioria é React, e para o dia em que um projeto justificar a mudança — o vocabulário é o mesmo, só a embalagem muda.
De função para propriedade
NovoJS
A diferença de fundo: em JavaScript você manda animar; em React você descreve o estado e a biblioteca anima a diferença.
Repare que transition aceita exatamente as mesmas opções que você aprendeu na Parte 2 e na Parte 3: duration, ease, type: 'spring', bounce, stiffness. Nada disso precisa ser reaprendido.
Os dois recursos que nasceram no React
NovoJS
Animação de layout. Com layout, o elemento anima sozinho quando a posição ou o tamanho dele muda por qualquer motivo — um filtro reordenou a lista, um acordeão abriu, a janela mudou de tamanho. Isso é difícil de fazer à mão porque exige medir antes e depois e inverter a diferença.
react
{itensFiltrados.map(i => (
<motion.li key={i.id} layout>{i.nome}</motion.li>
))}
// reordenou? cada item desliza para a posição nova, sozinho
Elemento compartilhado. Com o mesmo layoutId em dois componentes diferentes, a biblioteca entende que é "a mesma coisa" e faz um transitar no outro — a miniatura que cresce e vira a foto aberta.
react
<motion.img layoutId={`foto-${obra.id}`} src={obra.mini} />
// e, no modal aberto:
<motion.img layoutId={`foto-${obra.id}`} src={obra.grande} />
E o equivalente em HTML puro: desde junho de 2026, o Motion em JavaScript puro faz os dois com o animateView, no próximo tópico. E, para o segundo caso, existe ainda view-transition-name no CSS, nativo, que faz um elemento "viajar" entre duas páginas. Não é tão flexível, mas resolve o caso principal — a foto do card virando a foto da página do projeto — sem framework nenhum.
animateView: o layout animado, agora sem React
NovoJS
Desde a versão 12.41, de junho de 2026, o Motion em JavaScript puro tem o animateView. Ele faz, com a View Transitions API do próprio navegador, o que o layout faz no React: você muda o DOM do jeito que quiser, e os elementos marcados deslizam da posição antiga para a nova — sem medir nada, sem calcular posição.
A função que você passa só muda a página. O resto é encadeado: .add() diz quais elementos acompanhar, e os métodos seguintes valem para eles.
js — filtro de cardápio ou portfólio
const { animateView } = Motion;
const lista = document.querySelector('.catalogo');
// só muda o DOM: esconde o que não é da categoria
function filtrar(categoria) {
for (const item of lista.children) {
item.hidden = categoria !== 'tudo' && item.dataset.cat !== categoria;
}
}
function aoClicar(categoria) {
animateView(() => filtrar(categoria), { duration: 0.4 })
.add('.catalogo > li') // cada item ganha um view-transition-name
.enter({ opacity: [0, 1] }) // os que aparecem
.exit({ opacity: 0 }); // os que somem
// os que ficam deslizam sozinhos para o lugar novo
}
Método
Faz
.add(alvo)
Marca os elementos (seletor ou elemento) para acompanhar a mudança
.add(de, para)
Um elemento que vira outro — a miniatura que vira a foto grande
.layout(opcoes)
A curva e a duração do deslocamento
.enter() · .exit()
O que só aparece e o que só some na mudança
.new() · .old()
A imagem nova e a antiga de um mesmo elemento, cada uma com a sua animação
.class(nome)
Dá um view-transition-class para você estilizar no CSS
.crop(false) · .group(false)
Desligam o recorte automático e o agrupamento pelo DOM
As opções de tempo são as mesmas da Parte 3, inclusive mola: animateView(mudar, { type: 'spring', bounce: 0.3 }). A chamada devolve o mesmo objeto de controle do animate, com pause(), cancel() e finished.
Três cuidados. Onde o navegador não tem View Transitions, a mudança acontece sem animação — o filtro continua funcionando, só não desliza. Uma transição de tela por vez: se o visitante clicar de novo no meio, o Motion enfileira a próxima em vez de deixar a tela quebrar. E a documentação não fala de "reduzir movimento", então faça você, como na Parte 2: com prefers-reduced-motion: reduce, chame filtrar() direto, sem o animateView.
Mover em arco: arc() — por enquanto, só no React
NovoJS
Desde a 12.40, de maio de 2026, o Motion tem arc(): em vez de ir em linha reta de um ponto a outro, o elemento faz uma curva, como um objeto jogado. Ele entra na opção path da transição e vale para x e y — e para a animação de layout, que é onde mais aparece: a miniatura que voa em curva até virar a foto aberta.
react
import { arc, motion } from "motion/react";
// crie uma vez, fora do componente: reaproveitar deixa a interrupção mais suave
const emArco = arc({ strength: 0.5 });
<motion.div
animate={{ x: 200, y: 100 }}
transition={{ duration: 0.8, path: emArco }}
/>
Opção
Padrão
Faz
strength
0.5
Quanto a curva se abre. 0 é linha reta
peak
0.5
Onde fica o ponto mais alto do arco. 0.5 é no meio
direction
automático
'cw' (sentido horário) ou 'ccw' (anti-horário)
rotate
false
true gira o elemento acompanhando a curva
Por que está na parte de React. A documentação do Motion diz que o arc() também funciona com o animate() do JavaScript puro. Testamos em 29 de setembro de 2026, na versão 13.4.6, e não funciona: o elemento vai em linha reta, sem erro nenhum. O código-fonte explica — no JavaScript puro, o animate() de um elemento comum anima direto no estilo, sem o objeto intermediário que o path usa. Enquanto isso não mudar, arco em HTML puro sai de dois jeitos: dar uma curva diferente para cada eixo ({ x: { ease: 'linear' }, y: { ease: 'easeOut' } }, como na Parte 2) ou usar offset-path, o caminho de movimento do próprio CSS.
AnimatePresence é o único da lista sem equivalente honesto em HTML puro. Animar a saída de um elemento antes de removê-lo exige adiar a remoção até a animação acabar — em JavaScript puro você faz isso na mão, com await na animação antes do remove().
Quando migrar — e quando não
Novo
Situação
Vale React?
Landing page de serviço
Não. Nem de longe.
Site institucional de 6 páginas
Não — se o problema é repetição, veja a Parte 7 da trilha Fluxo de trabalho
Catálogo com filtro e reordenação animada
Não mais só por isso — o animateView faz a reordenação animada em HTML puro
Galeria com miniatura abrindo em modal
Não necessariamente — View Transitions cobre boa parte
Painel com muito estado que muda
Sim, mas aí a razão é o estado, não a animação
A pergunta de sempre: a animação nunca é motivo suficiente para adotar um framework. Se o React entrar no seu fluxo, que seja porque a interface tem estado complexo demais para o DOM na mão — e aí a animação vem de brinde, com a mesma API que você já conhece.
O mapa da API, numa página
Novo
referência rápida
// ── as cinco funções ────────────────────────────────────────────
animate(alvo, propriedades, opcoes) → objeto de controle
animate(de, para, opcoes) → anima um valor solto
animate([ [alvo, props, {at}], … ]) → sequência
scroll(callback | animacao, opcoes) → função de limpeza
inView(alvo, callback, opcoes) → função de limpeza
hover(alvo, callback) → função de limpeza
press(alvo, callback) → função de limpeza
stagger(segundos, {startDelay, from, ease}) → gerador de delay
animateView(mudarDOM, opcoes) → .add() .enter() .exit() .layout()
// ── opções de tempo ─────────────────────────────────────────────
duration delay repeat repeatType repeatDelay times ease
type: 'tween' | 'spring' | 'inertia'
spring novo: bounce, visualDuration
spring físico: stiffness, damping, mass, velocity, restDelta
por propriedade: { opacity: { duration: .2 }, y: { type:'spring' } }
path: arc({ strength, peak, direction, rotate }) (só React, por enquanto)
// ── curvas ──────────────────────────────────────────────────────
linear easeIn/Out/InOut backIn/Out/InOut circIn/Out/InOut
anticipate cubicBezier([a,b,c,d]) steps(n) função própria
// ── objeto de controle ──────────────────────────────────────────
play() pause() cancel() complete() stop()
time speed duration then()
// ── scroll ──────────────────────────────────────────────────────
{ container, axis:'x'|'y', target, offset:['start end','end start'],
trackContentSize }
callback(progresso, info) → info.y.{current,progress,velocity,scrollLength}
// ── inView ──────────────────────────────────────────────────────
{ root, margin:'0px 0px -15% 0px', amount:'some'|'all'|0..1 }
retorne uma função → ela roda quando o elemento SAI
// ── SVG e 3D ────────────────────────────────────────────────────
pathLength pathOffset pathSpacing
rotateX/Y/Z z transformPerspective (perspective vai no pai)
Esta é a folha para deixar aberta enquanto trabalha. O resto da trilha é sobre quando usar cada coisa — que é a parte que a documentação de qualquer biblioteca, por natureza, não pode responder por você.