Key answer: scroll-controlled product rotation maps the scroll position to a rotation angle so the visitor turns a 3D model by scrolling the page. Load the product as a GLTF, center and scale it, map scroll to a normalized 0 to 1 value, ease it, and each frame set the model's Y rotation from that value — for tilt on two axes, drive a target quaternion and slerp toward it. Keep the buy button, price and specs as real HTML beside the canvas so the page stays crawlable, cap devicePixelRatio, and give reduced-motion users a static three-quarter view. This guide loads a real model, wires the rotation, adds a tilt axis, and covers the performance and SEO details that separate a shippable product page from a demo.
Table of contents
- What scroll-controlled rotation is
- Load and normalize the model
- Map scroll to a rotation angle
- Smooth the rotation
- Two-axis tilt with quaternions
- Lighting a product for rotation
- Keep the commerce layer in HTML
- Performance for product pages
- Accessibility
- When rotation beats a turntable video
- How AETumi builds product rotation
- Technical proof on GitHub
- FAQ
- Related resources
- Conclusion
What scroll-controlled rotation is
Scroll-controlled product rotation turns the page's scroll into a turntable. As the visitor scrolls, a 3D product spins so every side is revealed in sequence — a shoe rotates to show its sole, a bottle turns to face its label, a device pivots to expose its ports. Unlike a drag-to-rotate viewer, scroll rotation is directed: you decide which angle appears at which point in the story.
The architecture is the same one that governs the whole 3D scroll hub: scroll becomes a normalized value between 0 and 1, and the model's rotation is a pure function of that value. That makes the reveal repeatable, scrubbable and easy to reason about, which matters far more on a commercial product page than any clever one-off animation.
Load and normalize the model
Real products come as GLTF or GLB files, and they never arrive centered or correctly scaled. Load the model, then recenter and fit it before you rotate anything, so the spin happens around the product's own axis rather than some arbitrary origin.
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
let model
new GLTFLoader().load('/models/aesport.glb', (gltf) => {
model = gltf.scene
const box = new THREE.Box3().setFromObject(model)
const center = box.getCenter(new THREE.Vector3())
model.position.sub(center) // recenter on origin
const size = box.getSize(new THREE.Vector3()).length()
model.scale.setScalar(4 / size) // fit to a consistent size
scene.add(model)
})
Recentering with the bounding box means the model spins in place instead of orbiting off-screen, and scaling by its diagonal makes every product fill the frame consistently regardless of the units the artist exported. This normalization step is what lets one scene handle many different models.
Map scroll to a rotation angle
With a centered model, rotation is one line driven by progress. Decide how many turns the product makes across the page and multiply.
let target = 0, angle = 0
addEventListener('scroll', () => {
const max = document.documentElement.scrollHeight - innerHeight
target = max > 0 ? scrollY / max : 0
}, { passive: true })
function frame() {
requestAnimationFrame(frame)
angle += (target - angle) * 0.08
if (model) model.rotation.y = angle * Math.PI * 2 // one full turn top to bottom
renderer.render(scene, camera)
}
frame()
Because rotation reads the eased progress and nothing else, you can preview the model at any angle by setting the value directly, which makes it trivial to line up a specific face — the label, the sole — with a specific caption. Multiply by Math.PI * 4 for two turns, or by a smaller factor to show just one side.
Smooth the rotation
The exponential ease in the loop above is what makes the product feel weighty rather than twitchy. A lower factor gives a heavy, premium spin that continues a beat after the scroll stops; a higher factor locks the rotation tightly to the scrollbar. For most products a value between 0.06 and 0.1 reads as controlled and expensive. This single number is the difference between a turntable that feels engineered and one that feels nervous, and it costs nothing to tune.
Two-axis tilt with quaternions
A pure Y spin is fine, but combining a spin with a tilt — turning while also nodding to show the top — invites gimbal problems if you set Euler angles directly. Build a target quaternion from the desired orientation and slerp toward it instead.
const targetQuat = new THREE.Quaternion()
const euler = new THREE.Euler()
function frame() {
requestAnimationFrame(frame)
angle += (target - angle) * 0.08
euler.set(angle * 0.5, angle * Math.PI * 2, 0) // tilt on X, spin on Y
targetQuat.setFromEuler(euler)
if (model) model.quaternion.slerp(targetQuat, 0.15)
renderer.render(scene, camera)
}
Slerp interpolates along the shortest arc between orientations, so a combined tilt-and-spin stays smooth and never flips. This is the robust way to choreograph multi-axis product reveals and it drops cleanly into reusable 3D components.
Lighting a product for rotation
A rotating product only sells if the lighting flatters every angle it passes through. Flat ambient light kills the material; a single harsh light leaves half the rotation in shadow. Use a soft hemisphere light for base fill plus one or two directional lights placed so highlights sweep across the surface as it turns.
scene.add(new THREE.HemisphereLight(0xffffff, 0x223344, 0.9))
const key = new THREE.DirectionalLight(0xffffff, 1.4); key.position.set(3, 4, 3)
const rim = new THREE.DirectionalLight(0x88aaff, 0.8); rim.position.set(-3, 1, -2)
scene.add(key, rim)
The moving highlight is what makes a metal or glossy product look real as it spins. On Three.js product scenes this rim-plus-key setup does most of the perceived-quality work for very little cost.
Keep the commerce layer in HTML
The whole point of a product page is the sale, and the sale lives in HTML, not in the canvas. The price, the variants, the add-to-cart button and the specifications must be real DOM elements so they are crawlable, copyable, keyboard-operable and testable. The canvas is decoration layered behind or beside them.
<section class="product">
<canvas id="scene" aria-hidden="true"></canvas>
<div class="buy">
<h1>AESPORT carbon running shoe</h1>
<p class="price">$189</p>
<a class="cta" href="/cart/add/aesport">Add to cart</a>
</div>
</section>
The rotation enhances the pitch; it never becomes the pitch. This separation keeps the page fast and indexable, and it is a hard requirement for commercial work delivered for agencies.
Performance for product pages
Product models are often detail-heavy, so performance discipline matters more here than on a simple primitive. Cap devicePixelRatio to 2, compress textures, and render on demand — repaint only when the rotation actually changes and pause when the canvas scrolls out of view. A resting product page should use no GPU at all. Draco-compress the GLB to cut download size, and test the reveal on a mid-range phone where a heavy model exposes jank first.
Accessibility
Respect reduced motion by presenting the product at a flattering static angle instead of spinning it. The visitor who opts out of motion still needs to see the product clearly.
if (matchMedia('(prefers-reduced-motion: reduce)').matches && model) {
model.rotation.y = Math.PI * 0.25 // static three-quarter view
renderer.render(scene, camera)
}
A three-quarter view reads as the most informative single angle for most products. With the price and buy button already in HTML, the page remains fully shoppable with motion off or WebGL unavailable.
When rotation beats a turntable video
A pre-rendered turntable video is simpler and sometimes the right call — for a single fixed spin on a page where nothing else is interactive, a video can be lighter than a WebGL scene. Scroll-controlled rotation wins when the reveal must sync precisely to scroll position, when you want to show specific faces at specific moments, or when the same model feeds a configurator or variant switcher elsewhere on the site. Choose real 3D when the interactivity earns its weight, and a video when it does not — the honest trade-off behind every interactive website decision.
How AETumi builds product rotation
AETumi is an AI-native 3D web platform for production Three.js and WebGL sites, React and Next.js components, and MCP workflows for assistants such as Claude Code. Product rotation in the AETumi standard follows this guide exactly: normalized GLTF, eased progress, quaternion slerp for multi-axis moves, an HTML commerce layer, and a reduced-motion static view. Because the model handling and rotation rig are componentized, the same setup can be scaffolded through an MCP workflow and pointed at a new product without rebuilding the scene.
Technical proof on GitHub
The AETumiApp/threejs-product-viewer repository demonstrates model loading, normalization and scroll-driven manipulation on a real product, and the AETumiApp/threejs-scroll-animation repository shows the progress pipeline that drives it. Read the code there and this article for the decisions around lighting, quaternions and the commerce layer.
FAQ
How do I rotate a 3D model on scroll in Three.js? Load the model as GLTF, recenter it with a bounding box, map scroll to a 0 to 1 value, ease it, and set model.rotation.y from that value each frame. Multiply progress by Math.PI times two for one full turn across the page.
How do I rotate on two axes without flipping? Build a target quaternion from the desired orientation and slerp the model's quaternion toward it, rather than setting Euler angles directly. Slerp follows the shortest arc and stays smooth.
Why does my model spin off-center? It is not centered on the origin. Compute its bounding box, subtract the center from its position, and scale by the box diagonal so it spins in place and fills the frame consistently.
Does scroll rotation hurt SEO? No, if the price, specs and buy button stay in HTML and the canvas is aria-hidden. Search engines index the DOM, so keep the commerce layer as real text and links.
Should I use a turntable video instead? Use a video for a single fixed spin with no other interaction. Use scroll-controlled 3D when the reveal must sync to scroll, show specific faces at specific moments, or share the model with a configurator.
Related resources
Continue with the 3D scroll hub, Three.js, React Three Fiber, reusable 3D components, interactive websites, the MCP workflow, and options for agencies.
Conclusion
Scroll-controlled product rotation is a turntable driven by one eased progress value, wrapped around a normalized model and an HTML commerce layer. Center the model, map scroll to angle, slerp for multi-axis tilt, light it so highlights sweep as it turns, and give reduced-motion users a clean static view. Keep the sale in the DOM and the spin becomes a selling tool that ships, not a demo that spins off-screen.
More from the AETumi library
Real, production-ready assets — preview the motion, grab the source.

