Appearance
Scroll-driven experiences
A scroll-driven sequence is deterministic: normalized page progress maps directly onto timeline time.
Basic hook
tsx
function useScrollProgress(section: HTMLElement | null) {
const [progress, setProgress] = useState(0)
useEffect(() => {
if (!section) return
const update = () => {
const rect = section.getBoundingClientRect()
const distance = rect.height - window.innerHeight
setProgress(Math.min(1, Math.max(0, -rect.top / distance)))
}
update()
window.addEventListener('scroll', update, { passive: true })
window.addEventListener('resize', update)
return () => {
window.removeEventListener('scroll', update)
window.removeEventListener('resize', update)
}
}, [section])
return progress
}Viewer integration
tsx
<ModelLabViewer
src="/models/galaxy.glb"
preset={preset}
playSequence={false}
sequenceProgress={progress}
effect={{
type: 'texture-vortex',
progress,
intensity: 0.9,
}}
/>UX guidance
- Keep the scroll section long enough for the movement to be readable.
- Disable orbit controls while the scroll timeline owns the camera.
- Respect
prefers-reduced-motionby using a static snapshot or a reduced sequence. - Avoid forcing scroll position.