O que Scroll Animation (GSAP + ScrollTrigger + Lenis) faz?
Se as skills oficiais do GreenSock (gsap-core, gsap-scrolltrigger, gsap-react, gsap-plugins) estiverem instaladas, use-as para detalhes da API; esta skill cobre a configuração do projeto, integração e os erros que quebram sites reais.
1. Instalação
npm i gsap lenis
npm i @gsap/react # React / Next.js apenas
Todos os plugins GSAP (ScrollTrigger, SplitText, Flip, ScrollSmoother, MorphSVG, DrawSVG…) estão incluídos no pacote público gsap e são gratuitos para uso comercial. Importe de gsap/<NomeDoPlugin> e chame gsap.registerPlugin(...) uma vez.
2. Um módulo de configuração
Crie lib/motion/setup.(js|ts) (ou animations/) que seja importado uma vez no cliente:
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { SplitText } from "gsap/SplitText";
import Lenis from "lenis";
import "lenis/dist/lenis.css";
gsap.registerPlugin(ScrollTrigger, SplitText);
export function initSmoothScroll() {
if (matchMedia("(prefers-reduced-motion: reduce)").matches) return null;
const lenis = new Lenis();
lenis.on("scroll", ScrollTrigger.update);
const tick = (time) => lenis.raf(time * 1000); // Tempo do GSAP em segundos, Lenis quer ms
gsap.ticker.add(tick);
gsap.ticker.lagSmoothing(0);
return () => { gsap.ticker.remove(tick); lenis.destroy(); };
}
Use ou Lenis ou GSAP ScrollSmoother, nunca ambos.
3. Integração com frameworks
React / Next.js (App Router) — arquivos que usam GSAP precisam de "use client". Sempre use useGSAP (limpeza automática, sem execução dupla no Strict Mode):
"use client";
import { useRef } from "react";
import gsap from "gsap";
import { useGSAP } from "@gsap/react";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(useGSAP, ScrollTrigger);
export function Features() {
const root = useRef<HTMLElement>(null);
useGSAP(() => {
const mm = gsap.matchMedia();
mm.add("(prefers-reduced-motion: no-preference)", () => {
gsap.from(".feature", {
autoAlpha: 0, y: 32, stagger: 0.08, ease: "power3.out",
scrollTrigger: { trigger: root.current, start: "top 75%" },
});
});
}, { scope: root }); // seletores têm escopo neste componente
return <section ref={root}>…</section>;
}
- Handlers de eventos que criam animações: envolva com
contextSafedouseGSAP. - Provedor de scroll suave: um componente cliente no layout raiz que chama
initSmoothScroll()em um effect (ouReactLenisdelenis/reactcomautoRaf: falsee o ticker do GSAP). - Mudança de rota no Next.js: scroll para o topo (
lenis.scrollTo(0, { immediate: true })) eScrollTrigger.refresh()após o novo monte da página.
Vue / Nuxt — crie em onMounted dentro de gsap.context(() => {…}, rootEl), chame ctx.revert() em onUnmounted. Nuxt: plugin somente cliente (plugins/gsap.client.ts).
Astro / multi-página estático — uma <script> no layout; com View Transitions, reinicialize em astro:page-load e mate os triggers em astro:before-swap.
Vanilla — um main.js que executa após o DOM pronto; agrupe por seção em gsap.context().
4. Regras do ScrollTrigger que previnem 90% dos bugs
- Coloque
scrollTriggerem uma timeline ou tween de nível superior — nunca em tweens aninhados dentro de uma timeline. - Use
ease: "none"comscrub. - A ordem de criação importa: crie triggers de cima para baixo na ordem da página (ou defina
refreshPriority), especialmente com pinning. - Fixe um wrapper, não o elemento que você anima. Não fixe dentro de um pai com
transform. EvitepinSpacing: falsea menos que intencional. - Use valores de função (
x: () => …) +invalidateOnRefresh: truepara qualquer coisa que dependa do tamanho da viewport. - Chame
ScrollTrigger.refresh()após imagens/fontes/conteúdo dinâmico alterarem o layout (window.addEventListener("load", …),document.fonts.ready). - Depure com
markers: true— remova antes de publicar. - Estados iniciais ocultos: use
gsap.from/fromToouautoAlphadefinidos por JS, para que o conteúdo fique visível sem JS. once: truepara revelações que não devem repetir (a maioria delas).
5. Responsivo e movimento reduzido
Envolva cada cena em gsap.matchMedia():
const mm = gsap.matchMedia();
mm.add({
isDesktop: "(min-width: 1024px)",
isMobile: "(max-width: 1023px)",
motionOK: "(prefers-reduced-motion: no-preference)",
}, ({ conditions: c }) => {
if (!c.motionOK) return; // layout estático, sem animação
if (c.isDesktop) { /* cenas fixas / horizontais */ }
if (c.isMobile) { /* apenas fades simples para cima */ }
});
matchMedia reverte automaticamente quando as condições mudam — sem limpeza manual.
6. Performance
- Anime
x/y/scale/rotation/autoAlpha(transformações + opacidade). Evite animarwidth/height/top/leftefilter: blur()pesado em áreas grandes. - Prefira uma timeline por seção em vez de dezenas de triggers separados;
ScrollTrigger.batchpara muitas revelações idênticas. gsap.quickTopara efeitos de seguir o ponteiro.- Imagens grandes em cenas com scrub: use
sizesadequados e evite redimensionar bitmaps enormes a cada quadro.
7. Verificação
Role a página inteira para cima e para baixo rapidamente; redimensione a janela; recarregue no meio da página; navegue para fora e volte (SPA) — sem saltos, pins duplicados ou erros no console. Teste com movimento reduzido ativado, em 375px e com limitação de CPU.
Receitas para efeitos comuns: ../motion-direction/references/patterns.md.