Motion Design para Web: Guia Completo
Scroll, texto, imagens e vídeo: o catálogo completo de técnicas de motion para web, com implementação em Next.js e os casos em que cada uma compensa.
- motion
- scroll

Sumário
- Índice
- 1. Fundamentos
- 2. Técnicas de Scroll Motion
- 3. Técnicas de Motion para Texto
- 4. Técnicas de Motion para Imagens
- 5. Técnicas de Motion para Vídeos
- 6. Transições de Página e Microinterações
- 7. Bibliotecas e Ferramentas
- 8. Implementação em Next.js
- 9. Sites de Referência e Inspiração
- 10. Performance e Acessibilidade
- 11. Roteiro de Estudo
Índice
- Fundamentos: os 2 tipos de animação por scroll
- Técnicas de Scroll Motion
- Técnicas de Motion para Texto
- Técnicas de Motion para Imagens
- Técnicas de Motion para Vídeos
- Transições de Página e Microinterações
- Bibliotecas e Ferramentas (o que cada uma faz)
- Implementação em Next.js (setup + código por técnica)
- Sites de Referência e Inspiração
- Performance e Acessibilidade
- Roteiro de Estudo Sugerido
1. Fundamentos
Antes de qualquer técnica, entenda a distinção mais importante do motion baseado em scroll. Praticamente tudo que você vê em sites premiados cai em uma dessas duas categorias:
1.1 Scroll-triggered (disparada por scroll)
A animação dispara uma vez quando o elemento entra no viewport e roda com duração própria (tempo). O scroll é apenas o gatilho.
- Exemplos: fade-in de cards, texto que sobe ao aparecer, contadores animados.
- Ferramentas típicas:
whileInView(Motion),ScrollTriggersem scrub (GSAP),IntersectionObserver, CSSanimation-timeline: view()comanimation-rangecurto.
1.2 Scroll-linked / scrubbed (vinculada ao scroll)
O progresso da animação é o progresso do scroll. Rolar para frente avança a animação, rolar para trás retrocede. Não existe duração em segundos; existe um intervalo de scroll.
- Exemplos: parallax, barra de progresso de leitura, vídeo "scrubado" estilo Apple, seções pinadas que se transformam conforme você rola.
- Ferramentas típicas:
ScrollTriggercomscrub(GSAP),useScroll+useTransform(Motion), CSSanimation-timeline: scroll().
1.3 Conceitos que você vai usar o tempo todo
| Conceito | O que é |
|---|---|
| Trigger / start / end | Ponto do viewport onde a animação começa e termina (ex.: start: "top 80%") |
| Scrub | Vincular progresso da timeline ao scroll (com ou sem "lag" suavizado, ex.: scrub: 1) |
| Pin | Fixar um elemento na tela enquanto um intervalo de scroll acontece "por baixo" |
| Stagger | Atraso incremental entre elementos de um grupo (letras, cards, linhas) |
| Lerp (interpolação linear) | Base do smooth scroll: a posição visual "persegue" a posição real do scroll |
| Easing | Curva de aceleração (ease-out para entradas, ease-in-out para movimentos) |
| Timeline | Sequência orquestrada de tweens com controle de sobreposição e ordem |
| FLIP | Técnica First-Last-Invert-Play para animar mudanças de layout com performance |
| Viewport amount / threshold | Quanto do elemento precisa estar visível para disparar (ex.: 30%) |
2. Técnicas de Scroll Motion
2.1 Reveal on Scroll (entrada de elementos)
A técnica mais comum da web. Elementos entram com fade + translate (geralmente opacity: 0 + translateY(30-60px) → estado final) quando atingem ~70-85% do viewport.
- Variações: slide lateral, scale-in (0.9 → 1), blur-in (
filter: blur(8px)→ 0), rotate sutil. - Boas práticas: distâncias curtas (20-60px), duração 0.5-0.9s,
ease-out, disparar apenas uma vez (once: true) em conteúdo informativo. - Como fazer:
whileInViewno Motion,ScrollTriggerno GSAP, ou 100% CSS comanimation-timeline: view().
2.2 Parallax
Camadas se movem em velocidades diferentes durante o scroll, criando profundidade. O fundo se move mais devagar (ou o primeiro plano mais rápido).
- Variações: parallax de background, parallax multicamadas (foreground/midground/background), parallax de textos sobre imagens, "depth parallax" com mouse + scroll combinados.
- Regra de ouro: deslocamentos sutis (10-30% da altura da seção). Parallax exagerado enjoa.
- Como fazer:
useScroll+useTransform(Motion),ScrollTriggercomscrub(GSAP), ou CSS puro comanimation-timeline: scroll().
2.3 Pinning (seções fixas / sticky sections)
Um elemento fica preso na tela enquanto o usuário "rola através" de um intervalo virtual. Base de quase toda narrativa scrollytelling.
- Usos: hero que se transforma, storytelling passo a passo, produto girando enquanto specs aparecem ao lado.
- Como fazer:
pin: trueno ScrollTrigger (o padrão do mercado), ouposition: sticky+ CSS scroll-driven animations para casos simples. - Cuidado: pinning cria espaçadores no layout (
pinSpacing); teste redimensionamento e mobile.
2.4 Scrub Animations (timeline controlada pelo scroll)
Uma timeline inteira (múltiplos elementos, sequência coreografada) avança e retrocede com o scroll. É o coração do scrollytelling moderno.
- Exemplo clássico: seção pinada onde, conforme você rola: título sai → imagem escala → texto 2 entra → cor de fundo muda.
- Como fazer:
gsap.timeline({ scrollTrigger: { scrub: 1, pin: true } }). O valor numérico do scrub (scrub: 0.5~1.5) adiciona suavização.
2.5 Horizontal Scroll
O usuário rola verticalmente, mas o conteúdo se move horizontalmente. Ótimo para galerias, cases, timelines e portfólios.
- Padrão de implementação: container pinado + translateX do conteúdo interno proporcional ao scroll vertical. A distância de scroll vertical "reservada" = largura horizontal a percorrer.
- Variações: seções horizontais com snap por painel, galerias horizontais dentro de página vertical, direção invertida (right-to-left).
- Cuidados: acessibilidade (navegação por teclado), mobile (às vezes é melhor virar scroll horizontal nativo com
overflow-x+ snap), e indicar visualmente que há mais conteúdo.
2.6 Smooth Scrolling (rolagem suavizada)
Substitui o scroll "seco" do navegador por uma rolagem com inércia e interpolação (lerp), dando a sensação "cinemática" dos sites de agência.
- Ferramenta padrão em 2026: Lenis (da Darkroom Engineering, ex-Studio Freight). Leve, funciona com scroll nativo (mantém
position: sticky, acessibilidade e SEO) e se integra a GSAP/Motion. - Alternativas: ScrollSmoother (GSAP, com efeitos de lag/velocidade por elemento), Locomotive Scroll v5 (agora construído sobre Lenis).
- Cuidado: em touch, o padrão é não suavizar (o momentum nativo do mobile já é bom). E
overflow-x: hiddenno body quebraposition: stickycom Lenis; useoverflow-x: clip.
2.7 Scroll Progress Indicator (barra de progresso)
Barra (geralmente no topo) que preenche conforme o progresso de leitura da página ou de um artigo.
- Como fazer:
useScroll().scrollYProgress→scaleX(Motion, 5 linhas), ou CSS puro comanimation-timeline: scroll(root)(zero JS).
2.8 Scroll Snapping
O scroll "encaixa" em seções ou itens (fullpage sections, carrosséis).
- Como fazer: CSS nativo
scroll-snap-type+scroll-snap-align(preferível), ousnapdo ScrollTrigger para encaixar em pontos da timeline. - Cuidado: snap fullpage agressivo frustra usuários; use
proximityem vez demandatoryquando possível.
2.9 Stacked / Sticky Cards (cartas empilhadas)
Cards ficam sticky e se empilham: o card seguinte sobe por cima do anterior, que escala levemente para trás e escurece. Muito comum em seções de features/serviços.
- Como fazer:
position: sticky; top: Xem cada card + scale/brightness controlados por scroll (MotionuseScrollcomtargetno container, ou ScrollTrigger, ou CSSview()).
2.10 Zoom / Scale on Scroll
Elemento (imagem, vídeo, texto gigante) escala conforme o scroll: hero que "abre" de um retângulo pequeno para fullscreen, ou imagem que dá zoom até revelar a próxima seção "dentro" dela.
- Como fazer: pin + scrub animando
scaleeclip-path/border-radius.
2.11 Velocity-based Effects (efeitos por velocidade de scroll)
Elementos reagem à velocidade do scroll, não só à posição: texto que inclina (skew) quando você rola rápido, marquee que acelera e inverte direção com a direção do scroll.
- Como fazer:
useVelocity(scrollY)no Motion,ScrollTrigger.getVelocity()no GSAP, ou o callback do Lenis (que expõevelocity).
2.12 Infinite Marquee (esteira infinita)
Faixa de logos/texto/imagens rolando em loop infinito. Frequentemente combinada com velocidade do scroll (item 2.11).
- Como fazer: duplicar o conteúdo e animar
xde 0 a -50% em loop (CSS ou GSAPxPercentcomwrap).
2.13 Scroll Storytelling / Scrollytelling
Combinação de tudo acima para contar uma narrativa: seções pinadas + scrub + texto sincronizado + mídia que muda por etapa. É o formato dos grandes especiais jornalísticos (NYT, Pudding.cool) e páginas de produto da Apple.
3. Técnicas de Motion para Texto
3.1 Split Text Reveal (revelação por caractere/palavra/linha)
O texto é quebrado em chars, words ou lines e cada pedaço anima com stagger. É a técnica de tipografia animada dos sites premiados.
- Variações:
- Por linha com máscara: cada linha sobe de dentro de um
overflow: hidden(efeito "cortina"). O visual mais elegante e o mais usado. - Por palavra: fade/slide com stagger de ~0.05s.
- Por caractere: entradas dramáticas de headlines (stagger 0.02-0.04s).
- Ferramentas: SplitText do GSAP (gratuito desde 2025, lida com acessibilidade e quebra responsiva de linhas),
splitTextdo Motion+, ou split manual em React (map de palavras →motion.span). - Cuidado: splits quebram com fontes que ainda não carregaram (use
document.fonts.ready) e precisam dearia-labelno elemento pai para leitores de tela (SplitText já resolve isso).
3.2 Text Reveal on Scroll (destaque progressivo)
Parágrafo longo em que as palavras "acendem" (de cinza para branco/preto) conforme o scroll avança, uma a uma. Popularizado por sites de agência e landing pages de IA.
- Como fazer: split em palavras + opacidade de cada palavra mapeada a uma fatia do
scrollYProgress, geralmente com a seção pinada.
3.3 Typewriter (máquina de escrever)
Caracteres aparecem em sequência, com cursor piscando. Bom para heros de produto dev/IA.
- Como fazer: bibliotecas como Typed.js, TextPlugin/SplitText do GSAP, ou implementação própria com
setInterval/RAF.
3.4 Scramble / Decode Text
Caracteres aleatórios "resolvem" no texto final (efeito Matrix/hacker).
- Como fazer: ScrambleTextPlugin do GSAP (gratuito), ou implementação própria trocando chars aleatórios a cada frame.
3.5 Kinetic Typography (tipografia cinética)
Texto gigante como elemento gráfico principal: palavras que se movem em direções opostas no scroll, títulos que atravessam a tela, texto em círculo girando, texto que distorce.
- Variações: linhas alternadas movendo para esquerda/direita com scrub; texto em path SVG (círculo/curva) girando com o scroll (
MotionPathPlugin); skew por velocidade.
3.6 Variable Font Animation
Animação dos eixos de uma fonte variável (font-variation-settings: weight, width, slant) por scroll, hover ou proximidade do mouse.
- Como fazer: CSS transitions/animations no
font-variation-settings, ou GSAP animando o valor. Efeito sofisticado e barato em performance quando usado com moderação.
3.7 Text Mask / Clip Reveal
Texto revelado por clip-path ou background-clip: text (gradiente que se move dentro do texto, imagem/vídeo "dentro" das letras).
- Como fazer:
background-clip: text+background-positionanimado; ou vídeo commask/SVG<clipPath>em forma de texto.
3.8 Hover Effects em Links e Menus
Underline animado (scaleX da esquerda → direita), troca de caracteres no hover (rolagem vertical de letras duplicadas), preenchimento de fundo, magnetismo.
- Como fazer: CSS para underline/preenchimento; GSAP/Motion para roll de letras (duas cópias do texto num
overflow: hidden, translateY no hover).
3.9 Counter / Number Ticker
Números que contam de 0 até o valor final ao entrar no viewport (métricas, estatísticas).
- Como fazer:
useMotionValue+animate()no Motion, ou tween de um objeto{ value: 0 }no GSAP comsnap: 1, disparado porwhileInView/ScrollTrigger.
4. Técnicas de Motion para Imagens
4.1 Image Reveal (revelação de imagem)
A imagem entra com efeito de máscara em vez de simples fade:
- Clip-path reveal:
clip-path: inset(100% 0 0 0)→inset(0)(revela de baixo para cima; qualquer direção funciona). - Curtain reveal: um painel colorido cobre a imagem e desliza para fora.
- Scale + parent overflow: imagem em
scale(1.3)dentro de containeroverflow: hidden, ambos animando em direções opostas (o clássico "reveal de agência").
4.2 Parallax de Imagem Interna
A imagem se move dentro de um frame com overflow: hidden conforme o scroll (imagem 120% da altura do container, translateY de -10% a 10%). Dá vida a qualquer grid de fotos.
4.3 Ken Burns / Slow Zoom
Zoom lento e contínuo (scale 1 → 1.08 em 8-20s) em imagens de hero/background. Barato e eficaz.
4.4 Zoom Parallax / Scroll Zoom Gallery
Múltiplas imagens em camadas escalando em velocidades diferentes numa seção pinada, criando efeito de "mergulho" (popularizado por tutoriais do Olivier Larose).
4.5 Hover Distortion (WebGL)
Distorção líquida/ondulada da imagem no hover, transições com displacement maps entre imagens. O visual "high-end" de portfólios criativos.
- Como fazer: shaders WebGL via OGL, Three.js/React Three Fiber, ou bibliotecas prontas (curtains.js, hover-effect.js). Custo: complexidade + bundle.
4.6 Image Trail / Mouse Follow Gallery
Rastro de imagens seguindo o cursor (cada movimento "carimba" uma imagem que escala e some). Efeito de hero de sites criativos.
4.7 Grid/Gallery Choreography
Entradas coreografadas de grids: stagger em diagonal, colunas subindo em velocidades diferentes (coluna 1 sobe, coluna 2 desce no scroll), reordenação animada com FLIP.
- Como fazer: stagger do GSAP/Motion; Flip plugin do GSAP ou prop
layoutdo Motion para reordenações.
4.8 Image Sequence on Scroll (sprite de frames)
Sequência de dezenas/centenas de frames (JPEG/WebP) desenhados em <canvas> conforme o scroll, simulando um vídeo scrubado com controle perfeito. É como a Apple faz as páginas do AirPods/iPhone.
- Como fazer: pré-carregar frames → ScrollTrigger/useScroll mapeia progresso → índice do frame →
drawImageno canvas. (Código na seção 8.)
4.9 Comparação / Before-After Slider
Duas imagens sobrepostas com divisor arrastável ou controlado por scroll (clip-path na imagem de cima).
5. Técnicas de Motion para Vídeos
5.1 Video Scrubbing on Scroll
O currentTime do vídeo é controlado pelo progresso do scroll numa seção pinada. O usuário literalmente "toca" o vídeo rolando.
- Requisitos técnicos críticos: o vídeo precisa ser encodado com keyframes frequentes (idealmente todo frame, ex.:
ffmpeg -g 1), sem áudio, curto e otimizado. Sem isso, o scrub trava. - Alternativa mais confiável: image sequence em canvas (4.8), especialmente para iOS/Safari.
5.2 Autoplay on View (play/pause por visibilidade)
Vídeo dá play ao entrar no viewport e pausa ao sair (economia de bateria/dados e foco de atenção).
- Como fazer:
IntersectionObserver(ouuseInViewdo Motion) chamandovideo.play()/pause(). Vídeo precisa sermuted+playsInlinepara autoplay funcionar.
5.3 Video Reveal / Expand on Scroll
Vídeo começa pequeno (ou com border-radius grande) e expande para fullscreen conforme o scroll, geralmente pinado (padrão de homepage de SaaS moderno).
5.4 Background Video com Overlay Animado
Vídeo de fundo (muted, loop) com camadas de texto/gradiente animando por cima, com parallax leve entre as camadas.
5.5 Video Mask / Video em Texto
Vídeo visível apenas dentro de uma forma ou de tipografia (SVG <clipPath>/mask ou background-clip), com a máscara animando (escala/posição) no scroll.
5.6 Hover Preview
Thumbnail estática que vira vídeo em preview no hover (cards de portfólio/streaming). onMouseEnter → play; onMouseLeave → pause + reset.
5.7 Transições de Vídeo entre Seções
Vídeo pinado que troca de "capítulo" (jump para timestamps ou crossfade entre vídeos) conforme etapas de texto passam por ele. Base do scrollytelling com vídeo.
6. Transições de Página e Microinterações
6.1 Page Transitions (transições entre rotas)
- View Transitions API (nativa): o navegador tira "snapshot" do estado antigo e anima para o novo. Com
view-transition-name, elementos compartilhados (ex.: thumbnail → imagem grande) fazem morph automático entre páginas. Suporte amplo em 2026 (Chrome/Edge desde 111, Safari 18+, Firefox 129+ para same-document). No Next.js há integração experimental com o componente<ViewTransition>do React 19.2 (seção 8.9). - AnimatePresence (Motion): transições de entrada/saída controladas por JS, com mais liberdade criativa (curtains, wipes, overlays com contagem/logotipo).
- Curtain/Overlay transition: painel cobre a tela na saída e revela a nova página (padrão de sites de agência; em stacks não-React, o barba.js é a referência).
6.2 Preloader / Intro Animation
Tela de abertura com contador de porcentagem, logo animado ou palavras trocando, terminando num reveal da home. Use com parcimônia (custa tempo do usuário).
6.3 Microinterações
- Magnetic buttons: botão "atraído" pelo cursor dentro de um raio (lerp da posição do mouse).
- Custom cursor: cursor substituído por um círculo com lag (lerp), que reage a hovers (escala, texto "view", blend mode).
- Hover states ricos: cards com tilt 3D (rotateX/rotateY pelo mouse), brilho seguindo o cursor, ícones com micro-animações.
- Feedback de UI: botões com spring no clique, toggles animados, skeleton → conteúdo com crossfade, toasts com spring.
7. Bibliotecas e Ferramentas
7.1 As essenciais (o "stack padrão" de 2026)
| Ferramenta | O que faz | Quando usar |
|---|---|---|
| GSAP 3.13+ | A plataforma de animação JS mais completa. Timelines, easing avançado, e plugins: ScrollTrigger (tudo de scroll: trigger, scrub, pin, snap), ScrollSmoother, SplitText, Flip, Observer, Draggable, MotionPath, MorphSVG, DrawSVG, ScrambleText. 100% gratuito desde abril/2025 (após aquisição pela Webflow), incluindo todos os plugins antes pagos. Hook oficial useGSAP() via @gsap/react resolve o ciclo de vida no React. | Scrollytelling complexo, pinning, horizontal scroll, animação de texto profissional, SVG. É o padrão da indústria criativa (maioria dos sites premiados no Awwwards). |
| Motion v12+ (ex-Framer Motion) | Biblioteca de animação declarativa. No React: motion/react (props animate, whileInView, whileHover), AnimatePresence (animações de saída/unmount), layout (FLIP automático), hooks useScroll, useTransform, useSpring, useVelocity, useInView. Motor híbrido: usa WAAPI/ScrollTimeline nativos quando possível (animações fora da main thread) com fallback JS para springs/gestos. Renomeada de Framer Motion para Motion (projeto independente, motion.dev); import mudou de framer-motion para motion/react. | Animações de UI em React/Next.js: entradas, hover, layout animations, exit animations, scroll-linked simples/médio. A DX mais natural para componentes React. |
| Lenis 1.x | Smooth scroll com lerp sobre o scroll nativo (preserva sticky, acessibilidade, SEO). Integração React via lenis/react (<ReactLenis root>). Sincroniza com ScrollTrigger e Motion. Atenção: os pacotes antigos @studio-freight/react-lenis e @studio-freight/lenis foram descontinuados; use o pacote lenis. | Qualquer site que queira o "feel" premium de agência. Combina com GSAP ou Motion. |
| CSS Scroll-Driven Animations (nativo) | animation-timeline: scroll() (progresso do container) e view() (visibilidade do elemento) + animation-range. Roda na thread do compositor: performance imbatível, zero JS. Suporte em 2026: Chrome/Edge 115+, Firefox 132+, Safari 18+/26 (~85-90% global). Use @supports (animation-timeline: scroll()) para progressive enhancement. | Reveals, parallax simples, barras de progresso. Não cobre pin complexo, snap de timeline nem coreografia avançada (aí é GSAP). |
View Transitions API (nativa) + React <ViewTransition> | Transições entre estados do DOM e entre páginas, com morph de elementos compartilhados via view-transition-name. Integração experimental no Next.js (experimental.viewTransition). | Transições de rota e de estado (listas, filtros, thumbnail→detalhe) sem biblioteca. |
7.2 Complementares e especializadas
| Ferramenta | O que faz |
|---|---|
| React Three Fiber + drei | Three.js declarativo em React. Cenas 3D, shaders, WebGL. drei traz helpers (ScrollControls, câmeras, materiais). Para heros 3D e experiências imersivas. |
| OGL / curtains.js | WebGL minimalista para efeitos de imagem (distorção em hover, displacement) sem o peso do Three.js. |
| anime.js v4 | Biblioteca JS leve e elegante para animações de propriedades, SVG e timelines. Alternativa menor ao GSAP para casos simples/médios. |
| React Spring | Animações baseadas em física de mola no React. Alternativa ao Motion com filosofia "tudo é spring". |
| Lottie (lottie-react) / Rive | Reprodução de animações vetoriais criadas no After Effects (Lottie) ou no editor Rive (interativas, máquina de estados, mais leves). Ícones, ilustrações e mascotes animados. |
| Swiper / Embla Carousel | Carrosséis e sliders com gestos. Embla é headless e leve, ótimo com React. |
| AOS (Animate On Scroll) | Reveals via atributos data-aos. Simples, mas em 2026 é geralmente substituível por CSS scroll-driven animations ou Motion. |
| Theatre.js | Editor visual de timelines para coreografias complexas (inclusive 3D) com interface de keyframes. |
| barba.js | Transições de página em sites multi-page não-React. |
| Locomotive Scroll v5 | Smooth scroll + detecção de elementos; a v5 é construída sobre Lenis. |
| tsParticles / three-globe etc. | Partículas e efeitos decorativos. |
7.3 Como escolher (regra prática)
- Só entradas e reveals simples? CSS scroll-driven animations (com fallback
@supports) ou MotionwhileInView. - App React com UI animada (modais, listas, layout)? Motion.
- Scrollytelling, pin, horizontal scroll, split text? GSAP + ScrollTrigger (+ Lenis).
- Feel premium de agência? Lenis + GSAP (o combo mais usado em sites Awwwards).
- Transição de rota? View Transitions API; efeitos artísticos de saída,
AnimatePresence. - Imagens com distorção/3D? R3F ou OGL.
- Motion e GSAP convivem bem no mesmo projeto: Motion para UI, GSAP para as seções cinematográficas.
8. Implementação em Next.js
Tudo abaixo assume Next.js 15/16 (App Router) + TypeScript + React 19.
8.0 Regras de ouro no App Router
- Animação = client component. Todo componente com GSAP/Motion/Lenis precisa de
"use client"no topo. Mantenha as páginas como Server Components e isole a animação em componentes folha. - Nunca toque em
window/documentno render. Efeitos só emuseEffect/useGSAP. - Cleanup obrigatório. ScrollTriggers e listeners precisam ser destruídos no unmount (troca de rota no App Router não recarrega a página). O
useGSAPfaz isso automaticamente. - Fontes antes de SplitText. Aguarde
document.fonts.readyantes de splitar texto. - Respeite
prefers-reduced-motion(seção 10). - Hydration: para evitar "flash" de conteúdo no estado inicial errado, defina o estado inicial via CSS (ex.: classe com
opacity: 0) ou useinitialdo Motion, e evite divergência servidor/cliente.
8.1 Setup de pacotes
npm i gsap @gsap/react lenis motion8.2 Smooth scroll global com Lenis
// app/providers/smooth-scroll.tsx
"use client";
import { ReactLenis } from "lenis/react";
export function SmoothScroll({ children }: { children: React.ReactNode }) {
return (
<ReactLenis
root
options={{
duration: 1.1,
easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
smoothWheel: true,
syncTouch: false, // touch mantém o momentum nativo
}}
>
{children}
</ReactLenis>
);
}// app/layout.tsx
import { SmoothScroll } from "./providers/smooth-scroll";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="pt-BR">
<body>
<SmoothScroll>{children}</SmoothScroll>
</body>
</html>
);
}Integração Lenis + ScrollTrigger (necessária quando usar os dois):
// app/providers/smooth-scroll.tsx (versão integrada com GSAP)
"use client";
import { ReactLenis, useLenis } from "lenis/react";
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { useEffect } from "react";
gsap.registerPlugin(ScrollTrigger);
function GsapSync() {
const lenis = useLenis(() => ScrollTrigger.update());
useEffect(() => {
if (!lenis) return;
// GSAP passa a comandar o RAF do Lenis
const raf = (time: number) => lenis.raf(time * 1000);
gsap.ticker.add(raf);
gsap.ticker.lagSmoothing(0);
return () => gsap.ticker.remove(raf);
}, [lenis]);
return null;
}
export function SmoothScroll({ children }: { children: React.ReactNode }) {
return (
<ReactLenis root options={{ autoRaf: false, duration: 1.1 }}>
<GsapSync />
{children}
</ReactLenis>
);
}8.3 Reveal on scroll
Com Motion (o jeito mais rápido):
"use client";
import { motion } from "motion/react";
export function Reveal({ children }: { children: React.ReactNode }) {
return (
<motion.div
initial={{ opacity: 0, y: 40 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.3 }}
transition={{ duration: 0.7, ease: [0.21, 0.47, 0.32, 0.98] }}
>
{children}
</motion.div>
);
}Com GSAP + useGSAP (batch para vários elementos):
"use client";
import { useRef } from "react";
import gsap from "gsap";
import { useGSAP } from "@gsap/react";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger, useGSAP);
export function RevealSection() {
const container = useRef<HTMLElement>(null);
useGSAP(
() => {
gsap.from(".card", {
y: 60,
opacity: 0,
duration: 0.8,
ease: "power3.out",
stagger: 0.12,
scrollTrigger: {
trigger: container.current,
start: "top 75%",
once: true,
},
});
},
{ scope: container } // seletores ficam restritos ao container + cleanup automático
);
return (
<section ref={container}>
<div className="card">…</div>
<div className="card">…</div>
<div className="card">…</div>
</section>
);
}100% CSS (progressive enhancement, zero JS):
@supports (animation-timeline: view()) {
.reveal {
animation: reveal-up linear both;
animation-timeline: view();
animation-range: entry 0% entry 60%;
}
@keyframes reveal-up {
from { opacity: 0; transform: translateY(40px); }
to { opacity: 1; transform: none; }
}
}8.4 Parallax
Com Motion (useScroll + useTransform):
"use client";
import { useRef } from "react";
import { motion, useScroll, useTransform } from "motion/react";
export function ParallaxImage({ src }: { src: string }) {
const ref = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({
target: ref,
offset: ["start end", "end start"], // do momento em que entra até sair do viewport
});
const y = useTransform(scrollYProgress, [0, 1], ["-12%", "12%"]);
return (
<div ref={ref} style={{ overflow: "hidden", borderRadius: 12 }}>
<motion.img
src={src}
style={{ y, scale: 1.25, width: "100%", display: "block" }}
alt=""
/>
</div>
);
}Com GSAP:
useGSAP(() => {
gsap.to(".parallax-img", {
yPercent: 20,
ease: "none",
scrollTrigger: {
trigger: ".parallax-wrap",
start: "top bottom",
end: "bottom top",
scrub: true,
},
});
}, { scope: container });8.5 Seção pinada com timeline scrubada (scrollytelling)
"use client";
import { useRef } from "react";
import gsap from "gsap";
import { useGSAP } from "@gsap/react";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger, useGSAP);
export function PinnedStory() {
const container = useRef<HTMLDivElement>(null);
useGSAP(
() => {
const tl = gsap.timeline({
scrollTrigger: {
trigger: container.current,
start: "top top",
end: "+=3000", // 3000px de scroll "virtual" para a narrativa
scrub: 1, // suavização de 1s
pin: true,
},
});
tl.to(".step-1", { opacity: 0, y: -40 })
.fromTo(".hero-img", { scale: 0.6 }, { scale: 1 }, "<")
.fromTo(".step-2", { opacity: 0, y: 40 }, { opacity: 1, y: 0 })
.to(".bg", { backgroundColor: "#0e0e10" }, "<");
},
{ scope: container }
);
return (
<div ref={container} className="bg" style={{ height: "100vh", position: "relative" }}>
<p className="step-1">Primeiro ato…</p>
<img className="hero-img" src="/produto.png" alt="" />
<p className="step-2" style={{ opacity: 0 }}>Segundo ato…</p>
</div>
);
}8.6 Horizontal scroll
"use client";
import { useRef } from "react";
import gsap from "gsap";
import { useGSAP } from "@gsap/react";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger, useGSAP);
export function HorizontalGallery() {
const container = useRef<HTMLDivElement>(null);
const track = useRef<HTMLDivElement>(null);
useGSAP(
() => {
const getDistance = () =>
(track.current?.scrollWidth ?? 0) - window.innerWidth;
gsap.to(track.current, {
x: () => -getDistance(),
ease: "none",
scrollTrigger: {
trigger: container.current,
start: "top top",
end: () => `+=${getDistance()}`, // scroll vertical = distância horizontal
scrub: 1,
pin: true,
invalidateOnRefresh: true, // recalcula em resize
},
});
},
{ scope: container }
);
return (
<section ref={container} style={{ overflow: "hidden" }}>
<div ref={track} style={{ display: "flex", gap: 24, width: "max-content" }}>
{[1, 2, 3, 4, 5, 6].map((i) => (
<div key={i} style={{ width: "60vw", height: "70vh", flexShrink: 0 }}>
Painel {i}
</div>
))}
</div>
</section>
);
}8.7 Split text reveal (SplitText do GSAP)
"use client";
import { useRef } from "react";
import gsap from "gsap";
import { useGSAP } from "@gsap/react";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { SplitText } from "gsap/SplitText";
gsap.registerPlugin(ScrollTrigger, SplitText, useGSAP);
export function AnimatedHeadline({ text }: { text: string }) {
const ref = useRef<HTMLHeadingElement>(null);
useGSAP(async () => {
await document.fonts.ready; // evita split com fonte errada
const split = SplitText.create(ref.current, {
type: "lines,words",
mask: "lines", // cria o overflow:hidden por linha (efeito cortina)
autoSplit: true, // re-splita em resize/carregamento de fonte
});
gsap.from(split.words, {
yPercent: 110,
duration: 0.9,
ease: "power4.out",
stagger: 0.04,
scrollTrigger: { trigger: ref.current, start: "top 80%", once: true },
});
});
return <h1 ref={ref}>{text}</h1>;
}Versão sem GSAP (split manual + Motion):
"use client";
import { motion } from "motion/react";
export function WordReveal({ text }: { text: string }) {
return (
<h2 aria-label={text}>
{text.split(" ").map((word, i) => (
<span key={i} style={{ display: "inline-block", overflow: "hidden" }} aria-hidden>
<motion.span
style={{ display: "inline-block", marginRight: "0.3em" }}
initial={{ y: "110%" }}
whileInView={{ y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, ease: "easeOut", delay: i * 0.04 }}
>
{word}
</motion.span>
</span>
))}
</h2>
);
}8.8 Barra de progresso de leitura (Motion, 10 linhas)
"use client";
import { motion, useScroll, useSpring } from "motion/react";
export function ReadingProgress() {
const { scrollYProgress } = useScroll();
const scaleX = useSpring(scrollYProgress, { stiffness: 120, damping: 30 });
return (
<motion.div
style={{
scaleX,
transformOrigin: "0 0",
position: "fixed",
inset: "0 0 auto 0",
height: 4,
background: "#6366f1",
zIndex: 50,
}}
/>
);
}8.9 Transições de página (View Transitions no Next.js 16)
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
experimental: {
viewTransition: true, // experimental — valide antes de usar em produção
},
};
export default nextConfig;// app/template.tsx — anima cada navegação de rota
"use client";
import { unstable_ViewTransition as ViewTransition } from "react";
export default function Template({ children }: { children: React.ReactNode }) {
return <ViewTransition>{children}</ViewTransition>;
}/* globals.css — customiza a transição padrão */
::view-transition-old(root) {
animation: fade-out 0.25s ease both;
}
::view-transition-new(root) {
animation: fade-in 0.35s ease 0.05s both;
}
@keyframes fade-out { to { opacity: 0; transform: translateY(-8px); } }
@keyframes fade-in { from { opacity: 0; transform: translateY(8px); } }Para o efeito thumbnail → detalhe (shared element), dê o mesmo view-transition-name ao elemento nas duas rotas:
.product-card img { view-transition-name: var(--vt-name); }8.10 Vídeo com autoplay por visibilidade
"use client";
import { useEffect, useRef } from "react";
export function AutoPlayVideo({ src }: { src: string }) {
const ref = useRef<HTMLVideoElement>(null);
useEffect(() => {
const video = ref.current;
if (!video) return;
const io = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) video.play().catch(() => {});
else video.pause();
},
{ threshold: 0.5 }
);
io.observe(video);
return () => io.disconnect();
}, []);
return <video ref={ref} src={src} muted loop playsInline preload="metadata" />;
}8.11 Image sequence scrubada (estilo Apple)
"use client";
import { useRef } from "react";
import gsap from "gsap";
import { useGSAP } from "@gsap/react";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger, useGSAP);
const FRAME_COUNT = 120;
const frameSrc = (i: number) =>
`/sequence/frame-${String(i + 1).padStart(4, "0")}.webp`;
export function ScrollSequence() {
const container = useRef<HTMLDivElement>(null);
const canvasRef = useRef<HTMLCanvasElement>(null);
useGSAP(
() => {
const canvas = canvasRef.current!;
const ctx = canvas.getContext("2d")!;
canvas.width = 1600;
canvas.height = 900;
const images: HTMLImageElement[] = [];
const state = { frame: 0 };
for (let i = 0; i < FRAME_COUNT; i++) {
const img = new Image();
img.src = frameSrc(i);
images.push(img);
}
const render = () => {
const img = images[Math.round(state.frame)];
if (img?.complete) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
}
};
images[0].onload = render;
gsap.to(state, {
frame: FRAME_COUNT - 1,
snap: "frame",
ease: "none",
onUpdate: render,
scrollTrigger: {
trigger: container.current,
start: "top top",
end: "+=2500",
scrub: 0.5,
pin: true,
},
});
},
{ scope: container }
);
return (
<div ref={container} style={{ height: "100vh" }}>
<canvas ref={canvasRef} style={{ width: "100%", height: "100%", objectFit: "cover" }} />
</div>
);
}8.12 Stacked cards (Motion + sticky)
"use client";
import { useRef } from "react";
import { motion, useScroll, useTransform, MotionValue } from "motion/react";
function Card({ i, total, progress, children }: {
i: number; total: number; progress: MotionValue<number>; children: React.ReactNode;
}) {
const start = i / total;
const scale = useTransform(progress, [start, 1], [1, 1 - (total - i) * 0.04]);
return (
<motion.div
style={{
scale,
position: "sticky",
top: `calc(8vh + ${i * 24}px)`,
transformOrigin: "top center",
}}
>
{children}
</motion.div>
);
}
export function StackedCards({ items }: { items: string[] }) {
const ref = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({ target: ref, offset: ["start start", "end end"] });
return (
<div ref={ref}>
{items.map((item, i) => (
<Card key={i} i={i} total={items.length} progress={scrollYProgress}>
<div style={{ height: "70vh", borderRadius: 16, background: "#18181b" }}>{item}</div>
</Card>
))}
</div>
);
}8.13 Evitando os erros clássicos no Next.js
| Erro | Causa | Correção |
|---|---|---|
window is not defined | GSAP/Lenis rodando no servidor | "use client" + lógica dentro de useEffect/useGSAP |
| Animações "fantasma" após trocar de rota | ScrollTriggers antigos não destruídos | useGSAP com scope (cleanup automático) ou ScrollTrigger.getAll().forEach(t => t.kill()) no cleanup |
| Posições erradas de trigger | Imagens/fonts carregando depois do cálculo | ScrollTrigger.refresh() após load; dimensões fixas nas imagens (next/image com width/height) |
| Flash de conteúdo antes do estado inicial | Estado inicial aplicado só via JS | Estado inicial no CSS ou prop initial do Motion |
| Sticky quebrado com Lenis | overflow-x: hidden no body | usar overflow-x: clip |
| Scrub de vídeo travando | Encode sem keyframes frequentes | re-encodar (-g 1) ou migrar para image sequence |
| Bundle inflado | Importar plugins não usados / lib inteira | importar só o necessário; LazyMotion no Motion; dynamic import para seções pesadas (R3F) |
9. Sites de Referência e Inspiração
9.1 Galerias de premiação e curadoria (o essencial)
| Site | O que encontrar |
|---|---|
| Awwwards (awwwards.com) | A principal premiação de web design criativo. Filtros por tecnologia (GSAP, Three.js) e por estilo. O "Site of the Day" é a régua do mercado. |
| Godly (godly.website) | Curadoria enxuta e moderna, forte em motion e landing pages. Cada entrada tem vídeo de preview. |
| FWA (thefwa.com) | Premiação focada em experiências imersivas e experimentais (WebGL pesado). |
| CSS Design Awards (cssdesignawards.com) | Premiação alternativa ao Awwwards, com jurados da indústria. |
| SiteInspire (siteinspire.com) | Curadoria clássica com filtros por estilo/segmento. |
| Httpster (httpster.net) | Curadoria com personalidade, foco em tipografia e sites ousados. |
| Minimal Gallery (minimal.gallery) | Sites minimalistas bem executados (bom contraponto: motion contido e elegante). |
| Land-book (land-book.com) e Curated.design | Landing pages e sites de produto, ótimos para referência comercial/SaaS. |
| Savee (savee.it) | Moodboard colaborativo com muito material de motion e direção de arte. |
| Dark Mode Design, Footer.design, Seesaw (seesaw.website) | Curadorias de nicho úteis para detalhes específicos. |
9.2 Referência técnica com código
| Site | O que encontrar |
|---|---|
| Codrops (tympanus.net/codrops) | Tutoriais e demos de efeitos avançados (scroll, WebGL, tipografia) com código-fonte. A melhor escola prática de motion web. |
| CodePen (codepen.io) | Milhares de demos de ScrollTrigger, split text, parallax. Busque por "ScrollTrigger", "Lenis", "scroll-driven". |
| Osmo (osmo.supply) | Biblioteca de componentes/efeitos de motion prontos criada por devs premiados no Awwwards. |
| GSAP Showcase e Demos (gsap.com/showcase) | Sites reais feitos com GSAP + coleção oficial de demos por plugin. |
| Motion Examples (motion.dev) | 400+ exemplos oficiais copy-paste do Motion. |
| Olivier Larose (blog.olivierlarose.com) | Tutoriais em vídeo + código de efeitos de sites premiados em Next.js (zoom parallax, text reveal, curved menus). |
| Frontend Horse (frontend.horse) e Codrops Collective | Análises de como efeitos de sites premiados foram construídos. |
| scroll-driven-animations.style | Site de referência oficial (Bramus/Chrome) com demos e ferramentas de CSS scroll-driven animations. |
9.3 Sites de produto/estúdio para estudar (padrões reais)
- Apple.com (páginas de produto): a referência de image sequence scrubada e pinning narrativo.
- Linear.app, Vercel.com, Resend.com, Stripe.com: motion contido e funcional de produto SaaS.
- Estúdios premiados: Locomotive (locomotive.ca), Obys (obys.agency), Basement Studio (basement.studio), Unseen Studio, Active Theory, Lusion (lusion.co), Darkroom Engineering (darkroom.engineering). Analise com DevTools: quase sempre há Lenis + GSAP por baixo.
10. Performance e Acessibilidade
10.1 Performance
- Anime apenas
transformeopacity. São compostas na GPU sem reflow/repaint. Evite animarwidth,height,top,left,margin,box-shadow(anime um pseudo-elemento com opacity para sombras). - Prefira o compositor. CSS scroll-driven animations e o motor híbrido do Motion (WAAPI/ScrollTimeline) rodam fora da main thread e permanecem fluidos mesmo com JS pesado.
will-changecom moderação. Aplique só no que vai animar e remova depois; em excesso, consome memória.- Nunca use listeners de
scrollcrus para animação. Use ScrollTrigger/useScroll (que já otimizam com RAF) ou CSS nativo. - Meça: DevTools > Performance (procure long tasks e jank), FPS meter, e teste em um Android intermediário, não só no seu desktop.
- Imagens/vídeos:
next/image, WebP/AVIF,preloadinteligente das sequences, poster em vídeos. - Code splitting:
next/dynamicpara seções WebGL/3D;LazyMotion+mcomponent para reduzir o bundle do Motion.
10.2 Acessibilidade
prefers-reduced-motioné obrigatório. Usuários com sensibilidade vestibular podem passar mal com parallax/zoom.
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}// Em React (Motion)
import { useReducedMotion } from "motion/react";
const reduce = useReducedMotion();
// use `reduce` para trocar slide/parallax por um simples fade (ou nada)No GSAP, use gsap.matchMedia() com a condição (prefers-reduced-motion: reduce) para registrar versões alternativas.
- Conteúdo não pode depender da animação. Sem JS (ou com motion reduzido), tudo precisa estar legível.
- Texto splitado precisa de
aria-labelno elemento pai (spans soltos viram ruído no leitor de tela). O SplitText do GSAP moderno já cuida disso. - Horizontal scroll e pin: garanta navegação por teclado e não sequestre o scroll além do necessário.
- Autoplay de vídeo sempre
muted, e idealmente com controle de pausa visível para vídeos longos.
11. Roteiro de Estudo
Ordem sugerida, do fundamento ao avançado:
- Semana 1-2 — Fundamentos: CSS transitions/animations, easing,
transform/opacity,IntersectionObserver. Implemente reveals com CSS scroll-driven animations puras. - Semana 3-4 — Motion:
whileInView, variants + stagger,AnimatePresence,layout, depoisuseScroll+useTransform(progress bar, parallax, stacked cards). - Semana 5-7 — GSAP: tweens, timelines, easing; depois ScrollTrigger a fundo (trigger, scrub, pin, snap,
matchMedia). Reconstrua: horizontal scroll, seção pinada narrativa, image sequence. - Semana 8 — Texto: SplitText (linhas com máscara, chars com stagger), text reveal progressivo, marquee com velocidade.
- Semana 9 — Integração: Lenis + GSAP + Next.js App Router num projeto real (portfólio/landing). Adicione View Transitions.
- Contínuo: escolha 1 site do Awwwards/Godly por semana e reconstrua 1 efeito dele. É o método que mais acelera o aprendizado (Codrops e Olivier Larose ajudam muito aqui).
Documento gerado em 01/08/2026. Versões de referência: GSAP 3.13+ (gratuito, todos os plugins), Motion v12+ (motion/react), Lenis 1.3+ (lenis/react), Next.js 15/16 (App Router), React 19.2.
Leia também
Motions premium em Next.js: os 18 padrões do site da Plantica, passo a passo
O scroll vira CSS custom property e o CSS faz o resto: os 18 padrões de motion do site da Plantica em Next.js, com o prompt de implementação pronto de cada um.
- motion
- gsap
Motions premium em Next.js: os 18 padrões do site da ANAI, passo a passo
A arquitetura híbrida do site da ANAI — CSS vars, tweens por seção e canvas — destrinchada em 18 padrões de motion com prompts prontos.
- motion
- gsap
Motions premium em Next.js: os 15 padrões do site do Studio Modular, passo a passo
A arquitetura componentizada do site do Studio Modular — módulos com gates, scroll nativo e CSS de ponta — em 15 padrões de motion com prompts prontos.
- motion
- gsap


