Communitygithub.com

Animação de Scroll estilo Apple

Crie animações orientadas pelo scroll (scrollytelling) no estilo Apple usando HTML/CSS/JS puro, sem bibliotecas. Ideal para páginas onde elementos se montam, acendem, inclinam, expandem ou trocam conforme o usuário rola, como nas páginas de produto da Apple.

O que é Animação de Scroll estilo Apple?

Esta skill permite construir animações de scroll sofisticadas no estilo Apple, conhecidas como scrollytelling, utilizando apenas HTML, CSS e JavaScript puro, sem dependências externas. As animações são 'esfregadas' pela posição do scroll — rolar para frente avança o efeito e rolar para trás o reverte. O princípio fundamental é baseado em três ideias: pinar um elemento filho com posição sticky dentro de um contêiner alto, calcular um progresso de 0 a 1 a partir da posição do scroll, e aplicar transformações visuais (translação, rotação, escala, opacidade, border-radius) com base nesse progresso. A skill oferece efeitos prontos como montagem de itens, iluminação de palavras, stepper fixo, inclinação de dispositivo, cartões que surgem e expansão de vídeo/imagem. Também inclui regras práticas sobre uso de propriedades CSS individuais, otimização para telas altas, suporte a prefers-reduced-motion, e métodos de teste headless via protocolo DevTools do Chrome. O resultado é uma página interativa e envolvente, sem necessidade de bibliotecas, perfeita para portfólios, landing pages e demonstrações de produtos.

Funciona com~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/MananShukla007/apple-scroll-animation/tree/HEAD/skills/apple-scroll-animation

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

O que Animação de Scroll estilo Apple faz?

Os efeitos são esfregados pela posição do scroll, não acionados por um temporizador. Rolar para cima reproduz o efeito ao contrário. Nenhuma biblioteca necessária.

As três ideias

  1. Pino. Um wrapper alto (ex.: height: 300vh) contém um filho com position: sticky; top: 0; height: 100vh; overflow: hidden. O filho permanece na tela enquanto o wrapper passa rolando. A altura extra do wrapper é seu orçamento de animação.
  2. Progresso. Uma função transforma a posição do scroll em um número de 0 a 1:
    • seção fixa: clamp(-rect.top / (rect.height - innerHeight))
    • elemento entrando na viewport: clamp((innerHeight*a - rect.top) / (innerHeight*(a-b)))
  3. Conduza os visuais a partir do número. Defina translate, rotate, scale, opacity (e borderRadius) a cada quadro. Um único listener de scroll regulado com requestAnimationFrame recalcula tudo.

Motor pronto para colar: references/scroll-engine.js.

Efeitos que funcionam bem (escolha de 3 a 5, não todos)

EfeitoComo fazer
MontarItens começam espalhados pela borda da viewport (ângulo aleatório, rotação, escala .35, opacidade .2) e suavizam até sua posição de layout. Calcule o deslocamento de cada item para seu local natural uma vez (ao carregar/redimensionar) e multiplique por (1 - ease(p)). Distribua por índice (stagger).
Iluminar palavrasEnrole cada palavra em um span. Opacidade .18 → 1 conforme clamp(p * 1.25 * N - k).
Stepper fixostep = floor(p * N); a fração p*N - step preenche uma barra de progresso dentro do passo ativo. Clicar em um passo deve fazer scrollTo para a posição daquele passo.
Inclinar para planoperspective(1400px) rotateX((1-e)*22deg) translateY((1-e)*60px) scale(.84 + .16*e) para um mockup de telefone/dispositivo.
Cartões que surgemtranslate: 0 (1-e)*80px; scale: .9+.1e; opacity: e, com atraso por coluna (i % cols).
ExpandirVídeo/imagem scale .78 → 1 e borderRadius 44px → 28px.

Use ease = t => 1 - (1-t)**3 no progresso antes de aplicá-lo.

Regras (aprendidas da maneira difícil)

  • Use as propriedades CSS individuais translate, rotate, scale para esfregar, e mantenha transform para hover. Caso contrário, hover e scroll disputam a mesma propriedade.
  • Não combine uma classe .reveal de fade com uma transformação esfregada no mesmo elemento. Escolha um.
  • Coloque o título da seção dentro do painel fixo com o conteúdo. Se o painel contiver apenas o conteúdo e estiver centralizado, monitores altos mostram uma grande área em branco.
  • Mantenha a distância de scroll modesta. Cerca de N * 55vh + 100vh para um stepper de N etapas, 240-300vh para um efeito único. Usuários acham 700vh+ cansativo.
  • Limite o que é fixo ao que cabe em 100vh. Em telas curtas (max-height: 820px) e celulares, oculte subtítulos e extras, e mostre apenas o passo ativo em um stepper.
  • Movimento reduzido: se prefers-reduced-motion: reduce, retorne progresso 1 em todos os lugares (estado final, sem movimento). Não deixe coisas ocultas.
  • Sticky não precisa de overflow em ancestrais exceto overflow-x: hidden no body, que é aceitável. Evite overflow: hidden em qualquer wrapper entre o elemento sticky e a página.
  • Recompute no redimensionamento (os deslocamentos de montagem dependem do layout).
  • Anime apenas translate/rotate/scale/opacity/border-radius; evite propriedades de layout.
  • Evite um segmento de vídeo inicial/final muito pequeno: ao esfregar a reprodução de <video>, clipes curtos podem renderizar em branco.

Ordem típica da página

Hero (fixo, montar) → uma declaração simples (sem efeito) → prova/filme (expandir) → stepper fixo → mockup de dispositivo (inclinar para plano) → grade de cartões (surgir) → CTA. Alterne seções pretas e cinza claro para o estilo Apple; uma cor de destaque.

Testes

O --screenshot do Chrome headless não consegue rolar. Em vez disso, controle um Chrome real pelo protocolo DevTools:

  1. chrome --headless=new --remote-debugging-port=9333 --user-data-dir=/tmp/prof about:blank &
  2. Do Node (v22+ tem WebSocket global): busque http://localhost:9333/json, abra o webSocketDebuggerUrl da página, envie Emulation.setDeviceMetricsOverride, Page.navigate, então para cada posição Runtime.evaluate("scrollTo(0,Y)"), aguarde ~700 ms, Page.captureScreenshot.
  3. Escolha Y a partir dos offsetTop e alturas das seções (ex.: 0%, 60%, 100% de cada seção fixa).
  4. Verifique no desktop (1440x900), em uma tela grande (2000x1300) e em um celular (390x800), e confirme que document.documentElement.scrollWidth === innerWidth (sem overflow horizontal).

Checklist de entrega

  • Todo efeito conduzido pelo progresso do scroll, reversível
  • Fallback para movimento reduzido
  • Layout de celular verificado, sem scroll horizontal
  • Comprimento do scroll fixo modesto; sem espaço em branco em telas altas
  • Capturas de tela no início / meio / fim de cada seção fixa

Habilidades Relacionadas