← Biblioteca Promptfly
GrátisSnippetsNext.js · GSAP

Snippet: objeto que gira com o scroll

Componente React com GSAP que desenha uma sequência de frames num canvas. É o scroll que gira o objeto. Já vem com os frames do anel do VYTAL pra testar.

ScrollSequence.tsx
"use client";

// Objeto que gira com o scroll: uma sequência de frames desenhada num <canvas>.
// É o scroll que escolhe qual frame aparece, não um vídeo tocando sozinho.
// Instale: npm i gsap
// Os 102 frames abaixo são do anel do VYTAL, já hospedados. Troque pelos seus.

import { useEffect, useRef } from "react";
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";

gsap.registerPlugin(ScrollTrigger);

const FRAME_COUNT = 102;
const frameUrl = (i: number) =>
  `https://www.promptfly.com.br/templates/vytal-anel-inteligente/ring-frames/ring-${String(i + 1).padStart(3, "0")}.webp`;

export default function ScrollSequence() {
  const sectionRef = useRef<HTMLElement>(null);
  const canvasRef = useRef<HTMLCanvasElement>(null);

  useEffect(() => {
    const canvas = canvasRef.current;
    const ctx = canvas?.getContext("2d");
    if (!canvas || !ctx) return;

    let cancelled = false;
    const state = { frame: 0 };
    const images = Array.from({ length: FRAME_COUNT }, (_, i) => {
      const img = new Image();
      img.src = frameUrl(i);
      return img;
    });

    // Desenha o frame atual centralizado, sem distorcer (igual object-fit: contain).
    function draw() {
      const img = images[Math.round(state.frame)];
      if (!canvas || !ctx || !img.naturalWidth) return;
      const dpr = window.devicePixelRatio || 1;
      const w = Math.round(canvas.clientWidth * dpr);
      const h = Math.round(canvas.clientHeight * dpr);
      if (canvas.width !== w || canvas.height !== h) {
        canvas.width = w;
        canvas.height = h;
      }
      const scale = Math.min(w / img.naturalWidth, h / img.naturalHeight);
      const dw = img.naturalWidth * scale;
      const dh = img.naturalHeight * scale;
      ctx.clearRect(0, 0, w, h);
      ctx.drawImage(img, (w - dw) / 2, (h - dh) / 2, dw, dh);
    }

    const gsapCtx = gsap.context(() => {}, sectionRef);

    // Pré-carrega e decodifica TODOS os frames antes de ligar o scroll.
    // Sem isso a animação engasga nos primeiros giros.
    Promise.all(images.map((img) => img.decode().catch(() => undefined))).then(() => {
      if (cancelled) return;
      if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
        state.frame = FRAME_COUNT - 1;
        draw();
        return;
      }
      gsapCtx.add(() => {
        gsap.to(state, {
          frame: FRAME_COUNT - 1,
          ease: "none",
          onUpdate: draw,
          scrollTrigger: {
            trigger: sectionRef.current,
            start: "top top",
            end: "+=300%", // quanto maior, mais devagar o objeto gira
            pin: true,
            scrub: 0.2,
          },
        });
      });
      draw();
    });

    window.addEventListener("resize", draw);
    return () => {
      cancelled = true;
      window.removeEventListener("resize", draw);
      gsapCtx.revert();
    };
  }, []);

  return (
    <section ref={sectionRef} style={{ height: "100vh", background: "#0a0a0a" }}>
      <canvas ref={canvasRef} style={{ display: "block", width: "100%", height: "100%" }} />
    </section>
  );
}

Como usar

  1. 01Roda npm i gsap no seu projeto Next.js.
  2. 02Cria o arquivo ScrollSequence.tsx e cola o código.
  3. 03Coloca o componente na página com conteúdo antes e depois, pra ter por onde rolar.
Promptfly PremiumAcesso vitalício

Esse é 1 recurso. A biblioteca tem 51.

de R$ 129,90por R$ 47,90-63% Pagamento único e acesso vitalício.

  • Mentoria pronta: o método do primeiro site ao primeiro cliente
  • Os 15 templates de sites cinematográficos, com o prompt completo
  • As 10 animações de scroll Premium, com prompt e código
  • As skills cinematográficas pro Claude Code
  • Grupo VIP no Discord
  • Todo template novo incluído, sem pagar de novo
  • Pagamento via Hotmart, garantia de 7 dias