Apostila prática · Front-end & narrativa digital

Scrollytelling:
do básico ao muito avançado

Um guia de estudo completo — teoria, código funcional, demonstrações ao vivo dentro desta própria página e um capítulo inteiro sobre como transformar essa habilidade em trabalho remunerado.

14 capítulos 3 demos interativas embutidas CSS puro · Intersection Observer · Scrollama · GSAP · React Exercícios + projeto final

Role para começar

Sumário

O caminho de estudo

A ordem importa: cada capítulo assume o anterior. Os níveis vão de fundamento a muito avançado, e os capítulos finais conectam tudo ao mercado.

  1. O que é scrollytelling (e o que não é)fundamento
  2. A peça-chave: position stickybásico
  3. Intersection Observer: o motor em JS purobásico→médio
  4. Scrollama.js: o padrão do jornalismomédio
  5. GSAP ScrollTrigger: pin, scrub e timelinesmédio→avançado
  6. CSS Scroll-Driven Animations: sem JavaScriptavançado
  7. Scrollytelling em frameworks: React, Svelte, Vueavançado
  8. Técnicas muito avançadas: canvas, vídeo, WebGL, Lenismuito avançado
  9. Performance: 60fps ou nadaavançado
  10. Acessibilidade, mobile e UX responsávelessencial
  11. Estratégia narrativa: o roteiro antes do códigoestratégia
  12. Mapa de decisão: qual tecnologia usar quandocomparativo
  13. Mercado de trabalho: onde, como e quantocarreira
  14. Projeto final guiado + recursosprática

Capítulo 01 · nível: fundamento

Conceito

O que é scrollytelling (e o que não é)

Scrollytelling (scroll + storytelling) é a técnica de usar a rolagem da página como linha do tempo de uma narrativa: conforme o leitor rola, o conteúdo visual reage, se transforma e revela informação em etapas controladas.

A distinção crucial: em um site comum, rolar apenas move o conteúdo. Em uma peça de scrollytelling, rolar dirige o conteúdo. O scroll deixa de ser navegação e vira controle narrativo — como o botão de "avançar" de uma apresentação, só que contínuo, reversível e no ritmo do leitor.

Um pouco de história (você vai precisar disso em entrevistas)

O marco fundador é "Snow Fall: The Avalanche at Tunnel Creek" (The New York Times, 2012), reportagem multimídia que ganhou o Pulitzer e virou verbo na indústria ("to snowfall a story"). De lá pra cá a técnica se consolidou em três frentes:

Anatomia universal de uma cena

Quase todo scrollytelling, em qualquer tecnologia, se reduz a este esqueleto — memorize-o, pois todos os capítulos seguintes só variam a forma de implementá-lo:

passo 1 (texto rola)
passo 2 (texto rola)
passo 3 (texto rola)
GRÁFICO "GRUDADO"
(sticky)

muda a cada passo
que cruza o gatilho
  1. Elemento fixo (graphic): gráfico, mapa, foto, vídeo ou cena 3D que fica preso na tela.
  2. Passos (steps): blocos de texto que rolam por cima ou ao lado.
  3. Gatilhos (triggers): a lógica que detecta "o passo N chegou ao ponto X da tela" e dispara a mudança.
  4. Estados ou interpolação: a cena muda em saltos discretos (estado 1 → 2 → 3) ou continuamente amarrada ao pixel de scroll (scrubbing).
Vocabulário de trabalho

Pin = prender um elemento na tela durante um trecho do scroll. Scrub = amarrar o progresso de uma animação diretamente à posição do scroll (rolar para trás rebobina). Step/trigger = gatilho discreto. Parallax = camadas movendo em velocidades diferentes para dar profundidade. Esses quatro termos aparecem em vagas, briefings e documentação — use-os corretamente.

Quando usar — e quando não usar

Visão de mercado

Em entrevistas e propostas, saber dizer não ao scrollytelling vale tanto quanto saber fazê-lo. Clientes pedem "uma página tipo a da Apple" para conteúdos que não se beneficiam disso. Quem diagnostica ("seu conteúdo é de consulta, não de narrativa — sugiro X") se posiciona como consultor, não como executor de pedidos.

Exercício 1

Abra duas referências: uma peça do The Pudding (pudding.cool) e uma página de produto da Apple. Para cada uma, identifique por escrito: o que está fixo, o que rola, onde estão os gatilhos e se a animação é por estados ou scrubbing. Esse olhar de engenharia reversa será seu principal instrumento de aprendizado.

Capítulo 02 · nível: básico

Fundação técnica

A peça-chave: position: sticky

Antes de qualquer biblioteca, você precisa dominar o mecanismo que segura o elemento na tela. Hoje isso é uma linha de CSS — e entender seus limites evita 80% dos bugs de iniciante.

Um elemento sticky se comporta como normal até atingir o deslocamento definido (ex.: top: 0); a partir daí ele "gruda" na viewport — mas apenas enquanto estiver dentro do seu elemento pai. Quando o pai termina, ele é levado embora. É exatamente esse comportamento que cria a cena pinada de duração controlada.

estrutura mínima — HTML

<section class="cena">          <!-- pai alto: define a DURAÇÃO da cena -->
  <div class="grafico">…</div>  <!-- fica grudado -->
  <div class="passos">
    <div class="passo">Passo 1</div>
    <div class="passo">Passo 2</div>
    <div class="passo">Passo 3</div>
  </div>
</section>

CSS

.cena {
  position: relative;          /* contexto do sticky */
}
.grafico {
  position: sticky;
  top: 0;
  height: 100vh;               /* ocupa a tela inteira */
}
.passo {
  min-height: 100vh;           /* cada passo "dura" uma tela */
}
Demo ao vivo 01 — stickyrole dentro da caixa ↓
position: sticky — eu fico grudado 📌
↓ continue rolando…
o elemento segue preso à borda superior
…até o fim do pai, quando ele vai embora ↑
Fim da cena. O sticky "soltou" porque o elemento pai acabou. A altura do pai controla a duração do efeito.
Esta demo usa somente HTML + CSS. Zero JavaScript. É a fundação de todo o resto.

As três armadilhas clássicas do sticky

  1. Um ancestral com overflow: hidden/auto entre o elemento e a página quebra o sticky silenciosamente. É o bug número 1 — inspecione a cadeia de ancestrais.
  2. Pai sem altura extra: se o pai tem a mesma altura do elemento sticky, não há espaço para "grudar". A duração vem da diferença de altura.
  3. Ancestral com display: flex esticando os filhos: use align-self: flex-start no elemento sticky.
Dica de profissional

Com sticky + a propriedade de CSS transition + a pseudo-classe certa, dá para fazer efeitos simples sem nenhuma linha de JS. Mas para reagir à posição do scroll você precisará de gatilhos — assunto do próximo capítulo. Regra prática: sticky segura, gatilho dispara.

Exercício 2

Reproduza a demo acima num arquivo local: uma seção de 300vh com um card sticky. Depois quebre-a de propósito das três formas listadas acima e conserte. Saber quebrar é o que separa quem copia código de quem resolve bug em produção.

Capítulo 03 · nível: básico → médio

JavaScript nativo

Intersection Observer: o motor em JS puro

O IntersectionObserver é a API nativa do navegador que avisa quando um elemento entra ou sai de uma área da tela — sem escutar o evento de scroll a cada pixel. É o coração de quase todas as bibliotecas de scrollytelling.

Antes dela, usava-se window.addEventListener('scroll', …) com cálculos de getBoundingClientRect() a cada frame — caro e propenso a travar. O observer inverte a lógica: o navegador observa e só chama seu código quando algo relevante acontece. Assíncrono, eficiente, suportado em todos os navegadores modernos.

o padrão completo de scrollytelling com IO

// 1. selecione os passos
const passos = document.querySelectorAll('.passo');

// 2. crie o observador
const observador = new IntersectionObserver((entradas) => {
  entradas.forEach(entrada => {
    if (entrada.isIntersecting) {
      const indice = Number(entrada.target.dataset.passo);
      atualizarGrafico(indice);          // sua função
      entrada.target.classList.add('ativo');
    } else {
      entrada.target.classList.remove('ativo');
    }
  });
}, {
  // 3. o gatilho: uma "linha" a 50% da altura da tela
  rootMargin: '-50% 0px -50% 0px',
  threshold: 0
});

// 4. observe cada passo
passos.forEach(p => observador.observe(p));

Entendendo rootMargin — o conceito que ninguém explica direito

rootMargin: '-50% 0px -50% 0px' encolhe a área de detecção para uma linha imaginária no meio da tela. Um passo só é "intersectante" quando cruza essa linha. É assim que se cria o clássico "quando o texto chega ao centro, o gráfico muda". Ajuste os percentuais para mover a linha de gatilho para cima ou para baixo.

Demo ao vivo 02 — IO + gráfico stickyrole dentro da caixa ↓
Estado inicial — role os passos à direita
Passo 1Quando este card cruza o meio da caixa, o observador dispara e o gráfico assume o primeiro estado.
Passo 2Segundo estado: as barras se reorganizam. Note que rolar para trás reverte — a narrativa é reversível.
Passo 3Terceiro estado: um destaque em outra cor guia o olhar. Cada passo = uma ideia, uma mudança.
Passo 4Estado final. Este padrão (sticky + observer + estados) resolve a maioria dos scrollytellings editoriais.
Implementada com IntersectionObserver usando root = esta caixa e rootMargin: '-50% 0px -50% 0px'. O código está no fim desta página — inspecione!
Limite importante

O IntersectionObserver dá gatilhos discretos (entrou/saiu). Ele não fornece progresso contínuo pixel a pixel. Para scrubbing (animação amarrada ao scroll), você precisará de GSAP ScrollTrigger (cap. 5), CSS scroll-driven animations (cap. 6) ou cálculo manual com requestAnimationFrame (cap. 8).

Visão de mercado

Saber implementar isso sem biblioteca é diferencial em entrevista técnica: demonstra que você entende a plataforma, não só ferramentas. É uma pergunta comum em vagas de front-end pleno: "como você detectaria que um elemento entrou na tela sem escutar o evento de scroll?"

Exercício 3

Adapte o código acima para trocar a imagem de fundo de uma seção sticky a cada passo (um "slideshow controlado por scroll"). Depois adicione um indicador de passo atual (1/4, 2/4…). Publique no CodePen — será sua primeira peça de portfólio.

Capítulo 04 · nível: médio

Biblioteca especializada

Scrollama.js: o padrão do jornalismo padrão editorial

Scrollama é uma biblioteca minúscula (criada por Russell Samora, do The Pudding) que empacota o padrão do capítulo anterior numa API limpa. É a escolha padrão em redações e projetos de visualização de dados.

Por que usá-la se o IO puro funciona? Porque ela resolve os detalhes chatos: recálculo em resize, direção do scroll (subindo/descendo), progresso dentro do passo, e depuração visual da linha de gatilho. Em projeto editorial com prazo, isso economiza dias.

instalação

// via npm
npm install scrollama

// ou via CDN, direto no HTML
<script src="https://unpkg.com/scrollama"></script>

uso típico

const scroller = scrollama();

scroller
  .setup({
    step: '.passo',     // seletor dos passos
    offset: 0.5,        // linha de gatilho a 50% da tela
    progress: true,     // habilita progresso contínuo 0→1 no passo
    debug: false        // true mostra a linha de gatilho na tela!
  })
  .onStepEnter(({ element, index, direction }) => {
    element.classList.add('ativo');
    atualizarCena(index, direction); // direction: 'up' | 'down'
  })
  .onStepProgress(({ index, progress }) => {
    // progress vai de 0 a 1 dentro do passo → permite interpolação!
    barra.style.transform = `scaleX(${progress})`;
  })
  .onStepExit(({ element }) => {
    element.classList.remove('ativo');
  });

// recalcular após imagens carregarem ou layout mudar
window.addEventListener('resize', scroller.resize);

O trio editorial: Scrollama + sticky + D3

Em jornalismo de dados, o padrão-ouro é: CSS sticky segura o gráfico, Scrollama dispara os passos, D3.js transiciona o gráfico entre estados (selection.transition().duration(600)). Cada onStepEnter chama uma função de estado do D3. Se você mira vagas de data visualization engineer ou infografia, esse trio é o que aparece nos repositórios das redações.

Detalhe que denuncia amador

Use sempre direction para tratar o scroll para cima. A narrativa precisa funcionar de trás para frente — o leitor volta para reler. Se seu passo 2 só funciona vindo do 1, a peça quebra na primeira releitura.

Exercício 4

Refaça o exercício 3 trocando seu IO manual por Scrollama com debug: true. Depois use onStepProgress para fazer um elemento girar continuamente durante o passo 2 (ex.: rotate(progress * 360deg)). Compare a quantidade de código com a versão manual.

Capítulo 05 · nível: médio → avançado

Animação profissional

GSAP ScrollTrigger: pin, scrub e timelines padrão agências

Se Scrollama domina o jornalismo, o GSAP com o plugin ScrollTrigger domina agências, product marketing e sites premiados. É a ferramenta mais poderosa para amarrar animações complexas ao scroll — e desde 2024 é 100% gratuita, incluindo os plugins antes pagos.

Os três superpoderes

  1. pin — prende qualquer elemento sem depender de sticky (o GSAP cria o espaçamento sozinho, com mais controle).
  2. scrub — amarra a timeline da animação ao scroll: rolar avança, voltar rebobina. scrub: 1 adiciona 1s de suavização ("catch up"), o segredo da sensação premium.
  3. Timelines — sequências coreografadas (fade → move → escala → troca de texto) distribuídas ao longo de um trecho de scroll.

cena pinada com scrubbing — o padrão "estilo Apple"

gsap.registerPlugin(ScrollTrigger);

const tl = gsap.timeline({
  scrollTrigger: {
    trigger: '.cena',
    start: 'top top',    // topo da cena encosta no topo da tela
    end: '+=3000',       // a cena "dura" 3000px de scroll
    pin: true,           // prende a cena
    scrub: 1,            // amarra ao scroll com 1s de suavização
    // markers: true    // réguas de depuração — use SEMPRE em dev
  }
});

tl.from('.produto',  { scale: 0.6, opacity: 0 })
  .to('.produto',    { rotation: 25, x: 200 })
  .from('.titulo-2', { y: 80, opacity: 0 }, '<') // '<' = junto com o anterior
  .to('.produto',    { scale: 3, opacity: 0 });

Leia start e end como "posição do gatilho + posição da viewport": 'top 80%' = quando o topo do elemento atinge 80% da altura da tela. Dominar essa sintaxe é dominar o ScrollTrigger.

Gatilhos discretos (sem scrub)

reveal clássico de entrada

gsap.utils.toArray('.card').forEach(card => {
  gsap.from(card, {
    y: 60, opacity: 0, duration: 0.8, ease: 'power2.out',
    scrollTrigger: {
      trigger: card,
      start: 'top 85%',
      toggleActions: 'play none none reverse'
      // onEnter onLeave onEnterBack onLeaveBack
    }
  });
});
Dica de produção

Três hábitos de quem entrega sem bugs: (1) markers: true durante todo o desenvolvimento; (2) ScrollTrigger.refresh() depois que fontes e imagens carregam (layout muda → gatilhos desalinham); (3) gsap.matchMedia() para criar animações diferentes (ou nenhuma) no mobile e respeitar prefers-reduced-motion nativamente.

Visão de mercado

"GSAP/ScrollTrigger" aparece nominalmente em vagas de creative developer e front-end para agências, e é praticamente pré-requisito para portfólios estilo Awwwards. Nos briefings de freelance, "animação tipo site da Apple" quase sempre se traduz em ScrollTrigger com pin + scrub (ou canvas, cap. 8).

Exercício 5

Monte uma seção pinada de 3000px em que: um título entra, um card gira e cresce, o fundo muda de cor, e um segundo título entra — tudo numa única timeline com scrub: 1. Depois converta para gatilhos discretos com toggleActions e compare as sensações. Saber escolher entre scrub e gatilho é decisão de design, não de código.

Capítulo 06 · nível: avançado

A fronteira do CSS

CSS Scroll-Driven Animations: sem JavaScript moderno

A especificação de animações dirigidas por scroll em CSS puro (animation-timeline) permite fazer scrubbing sem uma linha de JS — rodando fora da thread principal, com performance imbatível.

Duas timelines novas substituem o "relógio" da animação pelo scroll:

barra de progresso de leitura — sem JS

.barra-progresso {
  position: fixed; top: 0; left: 0;
  height: 4px; width: 100%;
  background: royalblue;
  transform-origin: left;
  animation: encher linear both;
  animation-timeline: scroll(root);  /* scroll da página */
}
@keyframes encher { from { transform: scaleX(0) } to { transform: scaleX(1) } }

reveal por elemento com view() e animation-range

.card {
  animation: surgir linear both;
  animation-timeline: view();
  /* anima só do momento em que ENTRA até cruzar 40% da tela */
  animation-range: entry 0% cover 40%;
}
@keyframes surgir {
  from { opacity: 0; transform: translateY(60px) }
}
Demo ao vivo 03 — CSS scroll-drivenrole dentro da caixa ↓
Seu navegador ainda não suporta animation-timeline — os cards aparecem sem animação (fallback correto!).
Barra verde no topo: animation-timeline: scroll(nearest) — enche conforme você rola esta caixa. Zero JS.
Estes cards: animation-timeline: view(nearest) — cada um anima a própria entrada enquanto atravessa a área visível.
Reversível de graça: role para cima e veja tudo rebobinar. Em JS isso exigiria código; aqui é o comportamento natural.
Fora da thread principal: mesmo com a página ocupada, estas animações não engasgam — rodam no compositor.
Envolvida em @supports (animation-timeline: scroll()): em navegadores sem suporte, o conteúdo aparece normalmente. Progressive enhancement de verdade.

A decisão profissional: suporte de navegadores

Chrome e Edge suportam há tempos; Firefox e Safari chegaram depois e versões antigas seguem em circulação. A postura correta em produção é tratar como aprimoramento progressivo:

/* base: funciona em qualquer navegador */
.card { opacity: 1 }

/* aprimoramento: só onde há suporte */
@supports (animation-timeline: view()) {
  .card { animation: surgir linear both; animation-timeline: view() }
}
Quando escolher CSS vs GSAP

CSS scroll-driven: efeitos por elemento (reveals, parallax, barras de progresso), performance crítica, projetos sem build JS. GSAP: coreografia entre muitos elementos, sequências com lógica, pin complexo, suporte garantido a navegadores antigos, controle fino de easing. Muitos projetos maduros usam os dois.

Exercício 6

Crie uma galeria de imagens em que cada foto entra com view() + animation-range e uma barra de progresso com scroll(root). Depois abra num navegador sem suporte (ou desative a flag) e confirme que tudo continua legível. Documente o fallback no README — recrutadores reparam nisso.

Capítulo 07 · nível: avançado

Ecossistema de frameworks

Scrollytelling em React, Svelte e Vue

No mercado, a maior parte das vagas envolve frameworks. A lógica é a mesma dos capítulos anteriores — o que muda é como integrar sem brigar com o ciclo de vida dos componentes.

React — as três rotas

Rota 1: Framer Motion (hoje "Motion") — a mais idiomática para produto/marketing:

progresso de scroll + parallax com useScroll

import { motion, useScroll, useTransform } from 'motion/react';

function Cena() {
  const ref = useRef(null);
  const { scrollYProgress } = useScroll({
    target: ref,
    offset: ['start end', 'end start']  // de "entra" até "sai"
  });
  // mapeia progresso 0→1 para valores visuais
  const y     = useTransform(scrollYProgress, [0, 1], ['-20%', '20%']);
  const scale = useTransform(scrollYProgress, [0, 0.5], [0.8, 1]);

  return (
    <section ref={ref}>
      <motion.img style={{ y, scale }} src="foto.jpg" />
    </section>
  );
}

// gatilho discreto de entrada:
<motion.div initial={{ opacity: 0, y: 40 }}
            whileInView={{ opacity: 1, y: 0 }}
            viewport={{ once: true, amount: 0.4 }} />

Rota 2: GSAP + @gsap/react — para coreografia pesada. O hook oficial useGSAP() resolve a dor histórica: limpeza automática dos ScrollTriggers quando o componente desmonta (sem isso, gatilhos "fantasmas" quebram a página em navegação SPA).

import gsap from 'gsap';
import { useGSAP } from '@gsap/react';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger, useGSAP);

function Cena() {
  const escopo = useRef(null);
  useGSAP(() => {
    gsap.to('.caixa', {
      x: 400,
      scrollTrigger: { trigger: '.caixa', scrub: 1 }
    });
  }, { scope: escopo });  // seletores restritos + cleanup automático
  return <div ref={escopo}><div className="caixa" /></div>;
}

Rota 3: react-scrollama — porta do Scrollama para o padrão editorial (steps + sticky) em React; comum em equipes de jornalismo que usam Next.js.

Svelte e Vue — em uma olhada

Armadilha de SSR (Next/Nuxt/SvelteKit)

Código de scroll só existe no navegador. Em SSR, proteja tudo: rode dentro de useEffect/onMounted, nunca no corpo do módulo; cheque typeof window !== 'undefined' quando necessário; e lembre que hidratação muda o layout — chame ScrollTrigger.refresh() (ou o equivalente) depois que fontes/imagens carregarem.

Exercício 7

Reimplemente a demo 02 desta apostila (gráfico + passos) como componente React com react-scrollama ou useScroll. Extraia um componente genérico <Scrolly steps={...} render={...} /> — componentes reutilizáveis de scrollytelling são excelente material de portfólio e de artigo técnico.

Capítulo 08 · nível: muito avançado

Alta produção

Canvas, vídeo, WebGL e smooth scroll

As técnicas deste capítulo são o que separa uma peça editorial competente de uma página de lançamento estilo Apple ou Awwwards — e o que justifica orçamentos de outra ordem de grandeza.

8.1 — Sequência de imagens em canvas (o efeito "Apple")

O produto que gira conforme você rola não é vídeo nem 3D: é uma sequência de frames JPG/WebP desenhada num <canvas>, com o índice do frame amarrado ao progresso do scroll. É a técnica das páginas do iPhone e AirPods.

esqueleto completo

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const TOTAL = 120;                       // nº de frames exportados
const frames = [];

// 1. pré-carregue os frames (essencial: sem isso, pisca)
for (let i = 0; i < TOTAL; i++) {
  const img = new Image();
  img.src = `/frames/frame-${String(i).padStart(4,'0')}.webp`;
  frames.push(img);
}

// 2. objeto animável + render
const estado = { frame: 0 };
function render() {
  const img = frames[Math.round(estado.frame)];
  if (!img?.complete) return;
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
}

// 3. GSAP interpola o índice do frame com o scroll
gsap.to(estado, {
  frame: TOTAL - 1,
  snap: 'frame',
  ease: 'none',
  scrollTrigger: { trigger: '.cena', pin: true, scrub: 0.5, end: '+=4000' },
  onUpdate: render
});

Regras de produção: frames em WebP/AVIF com qualidade ~70; 1000–1500px de largura bastam; 60–150 frames por cena; carregue os frames sob demanda em conexões lentas; e sirva uma imagem estática como fallback + prefers-reduced-motion.

8.2 — Vídeo com scrubbing

Amarrar video.currentTime ao scroll funciona, mas com pegadinhas sérias: a precisão do seek depende do encoding com keyframes frequentes. Reencode com FFmpeg forçando 1 keyframe por frame (-g 1) ou use a técnica de canvas acima, que é mais confiável. No iOS, vídeos exigem muted playsinline. Para peças críticas, o mercado converge para canvas.

8.3 — WebGL / Three.js dirigido por scroll

Para cenas 3D reais (câmera viajando por um ambiente, produto 3D interativo), o padrão é: a cena Three.js renderiza em loop, e o scroll controla parâmetros (posição da câmera, tempo da animação, uniforms de shader):

// scroll → progresso 0..1 → posição da câmera numa curva
gsap.to(progresso, {
  valor: 1, ease: 'none',
  scrollTrigger: { trigger: '.cena3d', pin: true, scrub: 1, end: '+=6000' },
  onUpdate: () => {
    const ponto = curva.getPointAt(progresso.valor);
    camera.position.copy(ponto);
    camera.lookAt(alvo);
  }
});

No ecossistema React, React Three Fiber + drei oferece <ScrollControls> e o hook useScroll() prontos para isso. É a stack típica dos sites vencedores de Awwwards.

8.4 — Smooth scroll com Lenis

Sites de alta produção quase sempre suavizam o scroll nativo com Lenis (sucessor espiritual do Locomotive Scroll), criando aquela inércia "amanteigada". A integração canônica com ScrollTrigger:

const lenis = new Lenis();
lenis.on('scroll', ScrollTrigger.update);
gsap.ticker.add((t) => lenis.raf(t * 1000));
gsap.ticker.lagSmoothing(0);
Julgamento profissional

Smooth scroll divide opiniões: melhora a percepção de animações scrubbed, mas sequestra parcialmente o scroll do usuário e pode enjoar. Use em peças imersivas de marketing; evite em conteúdo editorial longo e em qualquer produto utilitário. E sempre desligue sob prefers-reduced-motion.

8.5 — Seções horizontais e outros padrões de assinatura

Exercício 8 (projeto de fim de semana)

Exporte 90 frames de uma animação simples (Blender, After Effects, ou até um objeto girando gravado em vídeo e fatiado com FFmpeg: ffmpeg -i video.mp4 -vf fps=30 frame-%04d.webp) e implemente a cena canvas + pin + scrub completa, com imagem estática de fallback. Esta única peça, bem acabada, é um ímã de atenção em portfólio.

Capítulo 09 · nível: avançado

Engenharia

Performance: 60fps ou nada

Scrollytelling lento é pior que nenhum scrollytelling. A diferença entre "uau" e "que site travado" é engenharia — e é o assunto favorito de entrevistas técnicas sobre o tema.

As regras de ouro

  1. Anime só transform e opacity. Elas rodam no compositor (GPU) sem recalcular layout. Animar top/left/width/height/margin força reflow a cada frame — a causa nº 1 de engasgo. Precisa mover? translate. Precisa crescer? scale.
  2. Nunca faça trabalho pesado no evento de scroll. Se precisar escutá-lo, marque uma flag e processe num requestAnimationFrame:
    let agendado = false;
    window.addEventListener('scroll', () => {
      if (agendado) return;
      agendado = true;
      requestAnimationFrame(() => { atualizar(); agendado = false; });
    }, { passive: true });   // passive: não bloqueia a rolagem
  3. Evite layout thrashing: nunca alterne leitura (getBoundingClientRect, offsetHeight) e escrita de estilo no mesmo frame. Leia tudo, depois escreva tudo.
  4. will-change: transform com parcimônia — só nos elementos que realmente animam, aplicado pouco antes e removido depois. Espalhado por tudo, consome memória e piora o resultado.
  5. Mídia otimizada: imagens em AVIF/WebP com srcset; loading="lazy" no que está fora das cenas; frames de canvas pré-carregados antes da cena, não durante.
  6. Desconecte o que não se vê: pause vídeos, loops de rAF e cenas WebGL quando a seção sai da tela (o próprio IntersectionObserver serve de interruptor).

Como medir (e provar em code review)

Visão de mercado

Em portfólio, um estudo de caso "reduzi o tempo de frame de 38ms para 9ms nesta cena — eis como" vale mais do que três demos bonitas. Performance mensurável é linguagem de contratação sênior.

Capítulo 10 · nível: essencial

Responsabilidade

Acessibilidade, mobile e UX responsável

Scrollytelling malfeito exclui pessoas. Bem-feito, é apenas uma camada opcional sobre conteúdo sólido. Esta seção é inegociável em trabalho profissional — e diferencial competitivo, porque muita gente a ignora.

prefers-reduced-motion: a linha de base

/* CSS */
@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important }
}

// JS — cheque antes de inicializar animações
const reduz = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (!reduz) { iniciarAnimacoes() }

// GSAP faz isso elegantemente:
gsap.matchMedia().add('(prefers-reduced-motion: no-preference)', () => {
  /* animações só aqui */
});

Importante: reduzir movimento não significa esconder conteúdo. Os estados finais (texto visível, gráfico no estado certo) devem existir sem animação. Pessoas com distúrbios vestibulares passam mal com parallax e zoom — isso é saúde, não preferência estética.

Checklist de acessibilidade

Mobile: onde a maioria vai ler

Nunca faça

Scroll hijacking total (substituir a rolagem nativa por saltos de seção controlados por você) é quase sempre um erro: quebra expectativa, acessibilidade, busca na página e o botão "voltar" mental do usuário. Se o briefing pedir, apresente os custos — é papel do especialista.

Capítulo 11 · nível: estratégia

Antes do código

Estratégia narrativa: o roteiro vem primeiro

A maior causa de scrollytelling ruim não é técnica — é narrativa sem estrutura. Profissionais roteirizam antes de abrir o editor. Este capítulo é o seu processo de pré-produção.

O storyboard de scroll

Monte uma tabela simples antes de codar — ela vira o contrato com o cliente/editor e a especificação para você mesmo:

PassoO leitor lê…O visual mostra…O que MUDA (a transição)
1Contexto: o problema existeMapa do Brasil, tudo cinza
2O problema cresce no SudesteSudeste ganha cor + zoomzoom + preenchimento
3Um caso concretoZoom até uma cidade, foto entrazoom + crossfade
4A virada / soluçãoGráfico de linha subindotroca de cena + desenho da linha

Regra de ouro: cada passo precisa mudar a compreensão, não só a estética. Se a transição não ensina nada novo, corte-a. O teste: leia só a coluna "o leitor lê" — a história precisa se sustentar sozinha.

Padrões narrativos consagrados

Ritmo e respiro

Exercício 11

Escolha um tema que você domina e escreva o storyboard de 6 passos na tabela acima (sem codar!). Depois marque para cada transição qual técnica dos capítulos 2–8 a implementaria. Esse artefato — storyboard anotado com decisões técnicas — é exatamente o que se apresenta numa reunião de kickoff profissional.

Capítulo 12 · nível: comparativo

Mapa de decisão

Qual tecnologia usar, quando — a tabela definitiva

O que muda, na prática, conforme a tecnologia escolhida. Esta é a tabela para consultar em cada novo projeto (e para citar em entrevistas quando perguntarem "por que você escolheu X?").

AbordagemFaz bemNão faz / custa caroUse quando
CSS sticky puroPin simples, zero dependência, indestrutívelNão reage ao progresso do scroll sozinhoSempre — é a fundação das demais
Intersection ObserverGatilhos discretos, nativo, performáticoSem progresso contínuo; detalhes manuais (resize, direção)Reveals e steppers simples sem dependências
ScrollamaSteps editoriais com direção e progresso, API mínima, debug visualSó orquestra gatilhos — a animação em si é por sua conta (CSS/D3)Jornalismo de dados, relatórios, longform editorial
GSAP ScrollTriggerPin + scrub + timelines coreografadas, matchMedia, ecossistema maduroDependência ~50KB; curva de aprendizado da sintaxeMarketing, agências, qualquer coreografia complexa
CSS scroll-drivenScrubbing fora da thread principal, zero JS, reversível de graçaSuporte ainda desigual; sem lógica/encadeamento complexoReveals, parallax e progresso como aprimoramento progressivo
Motion (Framer)Idiomático em React, useScroll/useTransform elegantesPreso ao React; coreografias longas ficam verbosasProdutos e landings em React/Next
Canvas (sequência)Efeito "produto girando" com qualidade cinematográficaPipeline de assets (render, otimização, peso)Lançamento de produto de alto orçamento
Three.js / R3F3D real, câmera, shaders — teto criativo máximoComplexidade e custo altos; exige fallbacks sériosPeças imersivas premium, prêmios, hero de marca
No-code (Flourish, Shorthand, Webflow)Publicação em horas, sem programar; templates editoriais prontosPersonalização e identidade limitadas; custo de licença; lock-inRedações pequenas, marketing com prazo curto, protótipos para vender a ideia

Fluxo de decisão em 4 perguntas

  1. Precisa de scrubbing contínuo? Não → sticky + IO/Scrollama resolve. Sim → siga.
  2. É por elemento e simples? Sim → CSS scroll-driven com @supports. Não → siga.
  3. Há coreografia entre múltiplos elementos, pin longo, canvas ou 3D? Sim → GSAP ScrollTrigger (+ canvas/Three quando a produção pedir).
  4. O projeto vive num framework? Integre pela rota idiomática do cap. 7 (useGSAP, Motion, svelte-scroller, VueUse).
A resposta de entrevista pronta

"Começo pelo conteúdo: se os gatilhos são discretos, fico no nativo (sticky + IntersectionObserver ou Scrollama) pela leveza. Se há scrubbing e coreografia, GSAP ScrollTrigger com matchMedia para mobile e reduced motion. Efeitos por elemento eu já entrego como CSS scroll-driven com @supports, que roda fora da main thread. E tudo degrada para conteúdo legível sem JS." — Esse parágrafo, dito com segurança, demonstra o domínio completo desta apostila.

Capítulo 13 · nível: carreira

Mercado de trabalho

Onde, como e quanto: transformando a habilidade em renda

Scrollytelling é uma habilidade de nicho com demanda concentrada — o que é ótimo: pouca gente faz bem, e quem faz se destaca de forma desproporcional.

Os quatro mercados que contratam

O portfólio que contrata (plano concreto)

  1. Peça editorial: um scrollytelling de dados com tema real (dados públicos: IBGE, Our World in Data) usando sticky + Scrollama + um gráfico que evolui. Mostra narrativa e técnica.
  2. Peça de produto: uma landing fictícia (ou redesign de produto real, declarado como estudo) com GSAP: hero pinado, scrub, seção horizontal. Mostra acabamento.
  3. Peça de fronteira: a cena canvas do exercício 8 ou uma cena R3F. Mostra teto técnico.
  4. Um estudo de caso escrito por peça: problema → storyboard → decisões técnicas (cite a tabela do cap. 12) → performance medida → acessibilidade. É o texto que recrutadores e clientes realmente leem.

Publique cada peça com URL própria (GitHub Pages/Vercel), grave um vídeo de 30s de cada (nem todo recrutador rola até o fim) e poste o making-of no LinkedIn — conteúdo técnico de scrollytelling tem ótimo alcance orgânico porque é visual.

Precificação em freelance (raciocínio, não tabela mágica)

Como falar disso em entrevista

Posicionamento

O título que vende não é "sei GSAP" — é "transformo conteúdo em narrativas visuais que prendem o leitor até o fim", com métricas quando tiver (tempo na página, profundidade de scroll — meça com eventos de analytics nos seus próprios projetos!). Ferramenta muda; resultado é o que se compra.

Capítulo 14 · nível: prática final

Consolidação

Projeto final guiado + recursos

Um roteiro de 4 semanas (em ritmo de estudo paralelo ao trabalho) que termina com as 3 peças de portfólio do capítulo 13 publicadas.

Semana 1 — Fundação

Semana 2 — Peça editorial

Semana 3 — Peça de produto

Semana 4 — Peça de fronteira + lançamento

Template de partida (copie e cole)

Este esqueleto único junta o essencial dos caps. 2, 3 e 10 — é um scrollytelling completo e acessível em ~60 linhas, seu ponto de partida para qualquer peça editorial:

<!DOCTYPE html>
<html lang="pt-BR"><head><meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
  body{margin:0;font-family:Georgia,serif;line-height:1.6}
  .cena{position:relative;display:grid;grid-template-columns:1fr 1fr}
  .grafico{position:sticky;top:0;height:100svh;display:grid;place-items:center;
           background:#111;color:#fff;font-size:4rem;transition:background .6s}
  .passos{padding:50vh 8vw}
  .passo{min-height:100svh;display:flex;align-items:center;opacity:.25;transition:opacity .4s}
  .passo.ativo{opacity:1}
  @media(max-width:700px){
    .cena{grid-template-columns:1fr}
    .grafico{height:45svh;top:0}
  }
  @media(prefers-reduced-motion:reduce){*{transition:none!important}}
</style></head><body>

<section class="cena">
  <div class="grafico" id="g">1</div>
  <div class="passos">
    <div class="passo" data-i="0"><p>Primeiro ponto da história…</p></div>
    <div class="passo" data-i="1"><p>Segundo ponto…</p></div>
    <div class="passo" data-i="2"><p>Terceiro ponto…</p></div>
  </div>
</section>

<script>
const g = document.getElementById('g');
const estados = [
  {t:'1', cor:'#111'}, {t:'2', cor:'#0B46E8'}, {t:'3', cor:'#0E8A5F'}
];
const io = new IntersectionObserver(es => es.forEach(e => {
  e.target.classList.toggle('ativo', e.isIntersecting);
  if (e.isIntersecting) {
    const s = estados[e.target.dataset.i];
    g.textContent = s.t; g.style.background = s.cor;
  }
}), { rootMargin: '-50% 0px -50% 0px' });
document.querySelectorAll('.passo').forEach(p => io.observe(p));
</script>
</body></html>

Recursos para continuar

Último exercício — meta

Role esta apostila de novo, agora como engenheiro: identifique a barra de progresso, o trilho de capítulos, as três demos e como cada uma foi feita (o código-fonte está aberto — Ctrl+U / botão direito → inspecionar). Esta página foi construída, de propósito, apenas com as técnicas que ela ensina.