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.
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.
- O que é scrollytelling (e o que não é)fundamento
- A peça-chave: position stickybásico
- Intersection Observer: o motor em JS purobásico→médio
- Scrollama.js: o padrão do jornalismomédio
- GSAP ScrollTrigger: pin, scrub e timelinesmédio→avançado
- CSS Scroll-Driven Animations: sem JavaScriptavançado
- Scrollytelling em frameworks: React, Svelte, Vueavançado
- Técnicas muito avançadas: canvas, vídeo, WebGL, Lenismuito avançado
- Performance: 60fps ou nadaavançado
- Acessibilidade, mobile e UX responsávelessencial
- Estratégia narrativa: o roteiro antes do códigoestratégia
- Mapa de decisão: qual tecnologia usar quandocomparativo
- Mercado de trabalho: onde, como e quantocarreira
- 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:
- Jornalismo de dados — NYT, The Pudding, Reuters Graphics, The Guardian, e no Brasil os núcleos de infografia da Folha, Estadão e Nexo.
- Product marketing — as páginas de produto da Apple (iPhone, AirPods) popularizaram a sequência de imagens controlada pelo scroll; hoje é padrão em lançamentos de tech.
- Agências e prêmios — sites de Awwwards/FWA usam scroll como experiência imersiva (WebGL, 3D, vídeo).
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:
(sticky)
muda a cada passo
que cruza o gatilho
- Elemento fixo (graphic): gráfico, mapa, foto, vídeo ou cena 3D que fica preso na tela.
- Passos (steps): blocos de texto que rolam por cima ou ao lado.
- Gatilhos (triggers): a lógica que detecta "o passo N chegou ao ponto X da tela" e dispara a mudança.
- Estados ou interpolação: a cena muda em saltos discretos (estado 1 → 2 → 3) ou continuamente amarrada ao pixel de scroll (scrubbing).
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
- Use quando a informação tem sequência, transformação ou escala: evolução no tempo, antes/depois, zoom do macro ao micro, processo passo a passo, comparações que ganham força reveladas aos poucos.
- Não use para conteúdo de consulta rápida (documentação, e-commerce, dashboards), quando o usuário precisa de acesso direto (o scroll longo vira obstáculo), ou quando não há orçamento para fazer bem — um scrollytelling travado é pior que uma página estática boa.
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.
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 */
}
As três armadilhas clássicas do sticky
- Um ancestral com
overflow: hidden/autoentre o elemento e a página quebra o sticky silenciosamente. É o bug número 1 — inspecione a cadeia de ancestrais. - 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.
- Ancestral com
display: flexesticando os filhos: usealign-self: flex-startno elemento sticky.
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.
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.
root = esta caixa e rootMargin: '-50% 0px -50% 0px'. O código está no fim desta página — inspecione!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).
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?"
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.
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.
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
pin— prende qualquer elemento sem depender de sticky (o GSAP cria o espaçamento sozinho, com mais controle).scrub— amarra a timeline da animação ao scroll: rolar avança, voltar rebobina.scrub: 1adiciona 1s de suavização ("catch up"), o segredo da sensação premium.- 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
}
});
});
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.
"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).
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:
scroll()— o progresso da animação = progresso da rolagem de um contêiner. Uso clássico: barra de progresso de leitura.view()— o progresso = a passagem do elemento pela viewport. Uso clássico: reveal/parallax por elemento.
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) }
}
@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() }
}
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.
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
- Svelte: o utilitário
IntersectionObservervira uma action reutilizável (use:observar); a comunidade de dataviz (que adora Svelte, ex.: The Pudding migrou muito para Svelte) usa bibliotecas comosvelte-scroller, que entregaindexeprogressprontos por binding. - Vue:
@vueuse/coretrazuseIntersectionObservereuseScrollprontos; GSAP integra viaonMounted/onUnmountedcomScrollTrigger.kill()na desmontagem.
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.
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);
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
- Scroll vertical → deslocamento horizontal: pin na seção +
x: () => -(container.scrollWidth - innerWidth)com scrub. Ótimo para linhas do tempo e galerias. - Texto com máscara/clip revelado por scroll (headline gigante que se preenche) —
background-clip: text+ scrub. - Zoom infinito / travelling — camadas escalando em velocidades diferentes (parallax de profundidade extrema).
- Contadores e números que sobem ao entrar na tela — pequeno, mas onipresente em landing pages corporativas.
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
- Anime só
transformeopacity. Elas rodam no compositor (GPU) sem recalcular layout. Animartop/left/width/height/marginforça reflow a cada frame — a causa nº 1 de engasgo. Precisa mover?translate. Precisa crescer?scale. - 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 - Evite layout thrashing: nunca alterne leitura (
getBoundingClientRect,offsetHeight) e escrita de estilo no mesmo frame. Leia tudo, depois escreva tudo. will-change: transformcom parcimônia — só nos elementos que realmente animam, aplicado pouco antes e removido depois. Espalhado por tudo, consome memória e piora o resultado.- 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. - 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)
- DevTools → Performance: grave enquanto rola; procure barras vermelhas (frames >16ms) e blocos roxos de "Layout" dentro do scroll.
- Rendering → Paint flashing / Layer borders: mostra o que está sendo repintado e o que já está em camada de GPU.
- Teste em hardware real modesto — um Android intermediário de anos atrás, com CPU throttling 4x no DevTools. É onde seu público está.
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
- O conteúdo inteiro é legível com JavaScript desligado? (Ordem do DOM = ordem da narrativa.)
- Leitores de tela: gráficos têm texto alternativo/descrição; mudanças importantes usam
aria-livequando fizer sentido; nada essencial existe só como animação. - Teclado: a página funciona com PageDown/setas; nada de sequestrar teclas.
- Contraste e tamanho de texto mantidos durante todas as fases da animação (texto sobre vídeo é o vilão comum).
Mobile: onde a maioria vai ler
- Layout lado a lado vira empilhado: o gráfico sticky ocupa o topo (40–50vh) e os passos rolam por baixo — exatamente como a demo 02 desta apostila faz em telas estreitas.
- Barra de endereço que some/aparece muda a altura da viewport: prefira unidades
svh/dvhavhem cenas pinadas. - Menos é mais: corte cenas pesadas no mobile via
gsap.matchMedia()/media queries; ninguém sente falta do WebGL num 4G. - Teste o gesto real de rolagem com o dedão — a "velocidade de leitura" do polegar é outra; cenas precisam de mais folga.
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:
| Passo | O leitor lê… | O visual mostra… | O que MUDA (a transição) |
|---|---|---|---|
| 1 | Contexto: o problema existe | Mapa do Brasil, tudo cinza | — |
| 2 | O problema cresce no Sudeste | Sudeste ganha cor + zoom | zoom + preenchimento |
| 3 | Um caso concreto | Zoom até uma cidade, foto entra | zoom + crossfade |
| 4 | A virada / solução | Gráfico de linha subindo | troca 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
- Martini glass: narrativa guiada e linear (a haste da taça) que desemboca em exploração livre no final (a boca) — ex.: a história termina num mapa/gráfico interativo em que o leitor busca a própria cidade. O formato mais usado no jornalismo de dados.
- Zoom macro→micro: do panorama (o país, o planeta) ao caso individual (uma pessoa, uma rua). Gera empatia por afunilamento.
- Antes/depois contínuo: o scroll interpola dois estados (fotos de satélite, projeções). Simples e devastadoramente eficaz.
- Processo/desmontagem: o objeto se explode em partes conforme se rola (o padrão de páginas de produto e explicações de engenharia).
Ritmo e respiro
- Alterne cenas intensas (pinadas, animadas) com trechos de prosa comum — o leitor precisa descansar o olho e consolidar a informação.
- Dimensione a duração: passos de leitura confortável ≈ 1 tela de scroll cada; cenas scrubbed pinadas entre 2000 e 5000px. Mais que isso sem novidade visual = leitor rolando com raiva.
- Sinalize o mecanismo cedo: a primeira cena deve "ensinar" ao leitor que a página reage ao scroll (um movimento claro nos primeiros 100vh).
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?").
| Abordagem | Faz bem | Não faz / custa caro | Use quando |
|---|---|---|---|
| CSS sticky puro | Pin simples, zero dependência, indestrutível | Não reage ao progresso do scroll sozinho | Sempre — é a fundação das demais |
| Intersection Observer | Gatilhos discretos, nativo, performático | Sem progresso contínuo; detalhes manuais (resize, direção) | Reveals e steppers simples sem dependências |
| Scrollama | Steps editoriais com direção e progresso, API mínima, debug visual | Só orquestra gatilhos — a animação em si é por sua conta (CSS/D3) | Jornalismo de dados, relatórios, longform editorial |
| GSAP ScrollTrigger | Pin + scrub + timelines coreografadas, matchMedia, ecossistema maduro | Dependência ~50KB; curva de aprendizado da sintaxe | Marketing, agências, qualquer coreografia complexa |
| CSS scroll-driven | Scrubbing fora da thread principal, zero JS, reversível de graça | Suporte ainda desigual; sem lógica/encadeamento complexo | Reveals, parallax e progresso como aprimoramento progressivo |
| Motion (Framer) | Idiomático em React, useScroll/useTransform elegantes | Preso ao React; coreografias longas ficam verbosas | Produtos e landings em React/Next |
| Canvas (sequência) | Efeito "produto girando" com qualidade cinematográfica | Pipeline de assets (render, otimização, peso) | Lançamento de produto de alto orçamento |
| Three.js / R3F | 3D real, câmera, shaders — teto criativo máximo | Complexidade e custo altos; exige fallbacks sérios | Peças imersivas premium, prêmios, hero de marca |
| No-code (Flourish, Shorthand, Webflow) | Publicação em horas, sem programar; templates editoriais prontos | Personalização e identidade limitadas; custo de licença; lock-in | Redações pequenas, marketing com prazo curto, protótipos para vender a ideia |
Fluxo de decisão em 4 perguntas
- Precisa de scrubbing contínuo? Não → sticky + IO/Scrollama resolve. Sim → siga.
- É por elemento e simples? Sim → CSS scroll-driven com
@supports. Não → siga. - Há coreografia entre múltiplos elementos, pin longo, canvas ou 3D? Sim → GSAP ScrollTrigger (+ canvas/Three quando a produção pedir).
- O projeto vive num framework? Integre pela rota idiomática do cap. 7 (useGSAP, Motion, svelte-scroller, VueUse).
"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
- Jornalismo e comunicação de dados — redações (núcleos de infografia/dados), institutos de pesquisa, ONGs e relatórios anuais de empresas. Cargos: newsroom developer, data visualization engineer, editor de infografia. Stack valorizada: HTML/CSS/JS forte, D3, Scrollama, Svelte, sensibilidade editorial.
- Product marketing / tech — páginas de lançamento e sites institucionais de startups e big techs. Cargos: front-end de marketing, web engineer (brand/marketing team). Stack: React/Next, GSAP, Motion, performance e SEO.
- Agências e estúdios criativos — o mercado dos prêmios (Awwwards, FWA). Cargos: creative developer, creative technologist. Stack: GSAP, Three.js/R3F, WebGL, Lenis, shaders. Salários e projetos internacionais acessíveis por remoto.
- Freelance e consultoria — landing pages premium, relatórios interativos, peças de campanha. É onde a apostila vira renda mais rápido.
O portfólio que contrata (plano concreto)
- 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.
- 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.
- Peça de fronteira: a cena canvas do exercício 8 ou uma cena R3F. Mostra teto técnico.
- 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)
- Cobre por escopo, nunca por hora: defina nº de cenas/passos, complexidade de cada (tabela do cap. 12), responsividade e revisões incluídas.
- Ancoragem por valor: uma landing de lançamento com scrollytelling é peça de conversão/branding — precifique perto de "site institucional premium", não de "página HTML". Sinal prático: peças com cena canvas/3D custam tipicamente um múltiplo (2–4x) de uma landing animada com GSAP, que por sua vez custa um múltiplo de uma página estática.
- Cobre o storyboard como fase 1 paga: entrega o roteiro + protótipo simples; o cliente aprova antes da produção cara. Protege ambos e filtra clientes ruins.
- Inclua no contrato: nº de revisões, browsers suportados, prazos de conteúdo do cliente (o gargalo real), e o que acontece com pedidos fora do escopo.
Como falar disso em entrevista
- Domine o vocabulário do cap. 1 e a resposta-modelo do cap. 12.
- Prepare uma war story de performance (cap. 9) e uma de acessibilidade (cap. 10) — são os dois filtros de senioridade.
- Perguntas que costumam cair: "como detectar elemento visível sem evento de scroll?" (IO), "por que animar transform e não top?" (compositor), "como faria a página do iPhone?" (canvas + scrub), "e sem JavaScript?" (CSS scroll-driven).
- Leve o storyboard do exercício 11: entrevistas de creative developer frequentemente pedem raciocínio de concepção, não só código.
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
- Caps. 1–3 + exercícios. Entregável: o stepper com IO publicado no CodePen.
- Engenharia reversa de 2 peças (exercício 1) com anotações.
Semana 2 — Peça editorial
- Storyboard (cap. 11) de um tema com dados públicos.
- Implementação: sticky + Scrollama + gráfico SVG/D3 com 5 estados. Mobile empilhado.
prefers-reduced-motion. - Entregável: peça publicada + estudo de caso escrito.
Semana 3 — Peça de produto
- GSAP ScrollTrigger: hero pinado com timeline scrubbed, reveals com toggleActions, uma seção horizontal.
- Auditoria de performance com DevTools (cap. 9) documentada no estudo de caso.
Semana 4 — Peça de fronteira + lançamento
- Cena canvas (exercício 8) ou R3F ScrollControls.
- Vídeos de 30s, posts de making-of, portfólio no ar, CV atualizado com o vocabulário certo.
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
- Referências para dissecar: The Pudding (pudding.cool), NYT Graphics, Reuters Graphics, páginas de produto da Apple, vencedores do Awwwards e do prêmio Malofiej/SND (infografia).
- Documentação essencial: MDN (IntersectionObserver, position: sticky, scroll-driven animations), gsap.com/docs (ScrollTrigger), github.com/russellsamora/scrollama, motion.dev, lenis.darkroom.engineering, scroll-driven-animations.style (playground do Chrome team).
- Ferramentas no-code para conhecer (e indicar quando o cliente não tem orçamento de código): Flourish (scrolly de dados), Shorthand (longform editorial), Webflow (interações visuais).
- Comunidades: Data Visualization Society, fóruns do GSAP (excelente nível), News Nerdery (jornalismo de dados).
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.