Skip to main content

scroll-experiences

Composicao de experiencias dirigidas por scroll - cena fixa de fundo com conteudo rolando por cima, 3D ou canvas reagindo ao progresso do scroll, secoes pinadas, parallax e narrativa por scroll. Cobre a arquitetura que liga scroll, cena e conteudo, o carregamento progressivo do bundle WebGL, os fallbacks e o orcamento de performance. Use quando o pedido envolve "coisas acontecendo no fundo enquanto rola", cena 3D sincronizada com scroll, ou storytelling por scroll.

Aller à l'installation

Informations de source

Dépôt
brunoolf/frontend-craft
Dernière activité de la source
27 août 2026 à 18:21
Langue détectée de SKILL.md
portugais
Étoiles
0
Forks
0

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
scroll-experiences
description
Composicao de experiencias dirigidas por scroll - cena fixa de fundo com conteudo rolando por cima, 3D ou canvas reagindo ao progresso do scroll, secoes pinadas, parallax e narrativa por scroll. Cobre a arquitetura que liga scroll, cena e conteudo, o carregamento progressivo do bundle WebGL, os fallbacks e o orcamento de performance. Use quando o pedido envolve "coisas acontecendo no fundo enquanto rola", cena 3D sincronizada com scroll, ou storytelling por scroll.
# Experiências dirigidas por scroll As outras skills cobrem as peças: `gsap-scrolltrigger` a API, `threejs-*` a cena, `motion-web` o scroll-driven CSS. Esta cobre **a composição** — que é onde essas experiências quebram. ## Decida a categoria primeiro Cada uma tem arquitetura diferente. Escolher errado significa reescrever. | Categoria | O que é | Custo | |---|---|---| | **Reveal no scroll** | Elementos aparecem ao entrar no viewport | Trivial. CSS ou IntersectionObserver | | **Parallax** | Camadas em velocidades diferentes | Baixo. CSS scroll-driven resolve | | **Seção pinada** | A seção trava e o conteúdo interno avança com o scroll | Médio. ScrollTrigger com pin | | **Cena de fundo persistente** | Canvas ou WebGL fixo atrás, conteúdo rolando por cima | Alto. É o que a maioria quer ao dizer "coisas acontecendo no fundo" | | **Scrollytelling** | A narrativa *é* a cena; o texto legenda | Muito alto. Projeto próprio | **Antes de subir de categoria, pergunte se o efeito não sai na de baixo.** A maioria dos pedidos de "cena 3D de fundo" fica melhor resolvida com parallax em CSS mais um gradiente animado — sem 3MB de bundle e sem a bateria do celular. ## A arquitetura da cena de fundo A estrutura que funciona: ``` <div class="scene-root"> position: fixed; inset: 0; z-index: 0; <canvas> pointer-events: none (a menos que interativa) </div> <main> position: relative; z-index: 1; <section>…</section> conteudo rola normalmente </main> ``` Três regras que evitam os defeitos clássicos: 1. **A cena é `fixed`, não `sticky`.** Sticky dentro de um container que rola cria stacking context e a cena some atrás de conteúdo em pontos imprevisíveis. 2. **`pointer-events: none` no canvas**, a menos que a cena receba entrada. Senão ela come cliques e seleção de texto. 3. **O conteúdo tem `z-index` acima e fundo próprio** onde precisa de legibilidade. Texto direto sobre cena animada é ilegível em metade dos frames. ## Ligar scroll ao estado da cena **Nunca leia `scrollY` dentro do loop de render.** Isso força reflow síncrono a cada frame. O padrão é um único valor de progresso normalizado, atualizado por ScrollTrigger ou IntersectionObserver, lido pelo loop: ```js const state = { progress: 0, target: 0 } ScrollTrigger.create({ trigger: '#scene-range', start: 'top top', end: 'bottom bottom', onUpdate: (self) => { state.target = self.progress }, }) function frame() { // amortece: o scroll do usuario e irregular, a camera nao deve ser state.progress += (state.target - state.progress) * 0.08 camera.position.z = 5 - state.progress * 3 renderer.render(scene, camera) requestAnimationFrame(frame) } ``` O amortecimento (`lerp`) é o que separa uma cena que parece cara de uma que parece amadora. Scroll bruto produz movimento nervoso — especialmente em trackpad e em scroll por toque com inércia. **Um único `requestAnimationFrame` para a página inteira.** Múltiplos loops competindo é a causa mais comum de jank nessas páginas. ## Carregamento Um bundle WebGL não pode bloquear a primeira renderização. 1. **A página funciona e é legível sem a cena.** Renderize o conteúdo primeiro, sempre. 2. **Importe a cena dinamicamente**, depois do primeiro paint e só se ela for de fato ser vista. 3. **Estado de carregamento que não é um vazio.** Um gradiente estático ou um poster no lugar da cena. Um retângulo preto por dois segundos é pior que não ter cena. 4. **Não inicialize acima da dobra se a cena está abaixo dela.** Use IntersectionObserver na faixa da cena. ```js useEffect(() => { if (!inView || reducedMotion || !supportsWebGL()) return let dispose import('./scene').then((m) => { dispose = m.init(canvasRef.current) }) return () => dispose?.() }, [inView, reducedMotion]) ``` ## Fallbacks obrigatórios Quatro caminhos precisam existir. Faltando qualquer um, a experiência está incompleta: | Situação | Comportamento | |---|---| | Sem WebGL | Imagem estática ou gradiente CSS na faixa da cena | | `prefers-reduced-motion` | Cena estática num frame representativo, ou só o fundo. **Não** carregue o bundle | | Dispositivo fraco / bateria fraca | Reduza `pixelRatio`, desative postprocessing, corte partículas | | JavaScript falhou | O conteúdo está lá e legível. Sempre | `prefers-reduced-motion` numa experiência que *é* movimento não significa página quebrada: significa a mesma narrativa, contada estaticamente. O conteúdo e a ordem permanecem; só o movimento sai. ## Orçamento de performance - **60fps no dispositivo alvo mais fraco.** Meça lá, não no seu monitor. - **`pixelRatio` limitado:** `Math.min(devicePixelRatio, 2)`. Em telas 3x, renderizar nativo triplica o custo por nada visível. - **Pause fora da tela.** `IntersectionObserver` para parar o loop quando a cena sai do viewport, e `visibilitychange` para parar em aba de fundo. Loop rodando invisível é bateria queimada. - **Redimensionamento com debounce.** Recriar o renderer a cada pixel de resize trava a página. - **Limpe.** Geometrias, materiais, texturas e render targets têm `dispose()`. Cena não descartada em navegação SPA vaza memória de GPU até o crash. ## Mobile - Barra de endereço que aparece e some dispara `resize` constantemente. Use `100dvh`, não `100vh`, e faça debounce do handler. - Scroll por toque tem inércia própria — o amortecimento precisa ser mais forte que no desktop. - Considere não carregar a cena em telas estreitas. Uma imagem bem escolhida costuma superar uma cena 3D degradada. - Alvos de toque não podem cair sob o canvas. Verifique o `pointer-events`. ## Acessibilidade - Movimento não pode ser a única forma de acessar a informação. Se a cena revela conteúdo, esse conteúdo existe no DOM. - Sequestrar o scroll (`scroll-jacking`) quebra teclado, leitor de tela e a expectativa da pessoa. Se for inevitável, ofereça saída e navegação por teclado equivalente. - Nada que pisque entre 3 e 55 Hz. - Uma seção pinada ainda precisa ser alcançável por Tab, na ordem certa. ## Sinais de que passou do ponto - A pessoa não consegue chegar ao conteúdo sem assistir a uma sequência. - O scroll parece pesado ou "atrasado" em vez de amortecido. - A cena diz a mesma coisa que o texto ao lado — redundância cara. - O bundle da página passa de 1MB por causa de um efeito de fundo.
Voir sur GitHub