Pre-rendered frames of a 3D object played back frame-by-frame as you scroll. Apple AirPods Pro silicone tip rotation, every car configurator, every "explore the inside" page.
Instead of doing real-time 3D in WebGL, designers pre-render an object as 30–120 image frames (one per few degrees of rotation, or one per phase of an animation), then scrub through them as the user scrolls. The result feels like buttery-smooth 3D but is just images. It's how Apple's product pages play those "rotate the AirPods Pro" or "watch the iPhone unfold" sequences — and how every car configurator lets you spin the car.
Two implementations: (1) Stack all frames as position: absolute divs with opacity: 0, then on scroll set only the active frame's opacity to 1. Cheap, works for ~30 frames. (2) Production-grade: a <canvas> + JS that calculates which frame index matches the current scroll progress and calls ctx.drawImage(frames[i], 0, 0). Preload all frames before the section enters view. Apple's AirPods Pro page uses ~150 JPG frames totaling ~5MB.
Scroll inside. The orb rotates 360° — each scroll frame swaps to a different rendered angle. (24 simulated frames using CSS gradients.)
24 frames · scroll-scrubbed playback
Build a scroll-driven image sequence "scrubber" that plays back a 3D rotation frame-by-frame. Tall outer container with overflow-y:scroll containing a sticky pin (position:sticky top:0 height:100vh) inside an 1800px-tall section. Inside the pin: a 320×320 stage holding 24 absolutely-positioned circular frames stacked on top of each other (all inset:0, border-radius:50%, opacity:0 default, opacity:1 when .on). Each frame represents an angular step of a rotating 3D object — for the demo, generate them via CSS background gradient strings parameterized by angle (use document.createElement in JS), each frame's background is a radial-gradient highlight at a position based on cos/sin of the angle + a linear-gradient body — together they simulate a sphere rotating. JavaScript: on scroll, calculate the section's scroll progress (0 to 1) and pick frame index = floor(progress * 24). Toggle .on between frames. Update a "Frame N / 24" counter and a 180px progress bar. Add a soft shadow ellipse below the stage. Production version: replace generated frames with <img> tags preloaded from your rendered sequence (or use canvas + ctx.drawImage for >30 frames).