ScrollVars / fx
docs · demo · bench · llms.txt · GitHub

Three.js scene on the pin

A WebGL scene scrubbed by scroll. The canvas harness runs the lifecycle, Three renders.

Use it for: Hero 3D moments, product tours, camera paths, WebGL driven by the same variables.
Knobs: mountEffect({ context: null }) hands you the raw canvas; feed progress via onPin/onTravel closures

Install: npx scrollvars add three-scene
Styles: import 'scrollvars/styles/pin.css'

<div>   <!-- tracked in JS with pin: '250vh' -->
  <div class="sv-stage grid place-items-center">
    <canvas id="scene" class="h-[60vh] w-full max-w-xl"></canvas>
  </div>
</div>
<!-- same JS as the CSS tab, Tailwind only does the skeleton here -->
<div id="outer">                       <!-- no data-sv: tracked in JS below -->
  <div class="sv-stage"><canvas id="scene"></canvas></div>   <!-- from pin.css -->
</div>

<script type="module">
  import { track } from 'scrollvars'
  import { mountEffect } from 'scrollvars/canvas'
  import * as THREE from 'three'

  let progress = 0
  mountEffect(document.getElementById('scene'), {
    context: null,                       // you own the WebGL context
    setup(fx)  { /* renderer/scene/camera on fx.canvas */ },
    resize(fx) { /* setSize(fx.width, fx.height); setPixelRatio(fx.dpr) */ },
    frame(fx, dt) { /* advance + render; respect fx.reducedMotion */ },
  })
  track(document.getElementById('outer'), { pin: '250vh', onPin: (p) => (progress = p) })
  // The harness gives you: DPR cap, resize, pause offscreen/hidden tab,
  // delta-time loop, reduced-motion flag, cleanup, Three stays userland.
</script>
'use client'
const progress = useRef(0)
const canvasRef = useCanvasEffect({
  context: null,
  setup(fx)  { /* THREE renderer on fx.canvas */ },
  resize(fx) { /* size + DPR */ },
  frame(fx, dt) { /* render with progress.current */ },
})

<Track pin="250vh" onPin={(p) => (progress.current = p)}>
  <div className="sv-stage grid place-items-center">
    <canvas ref={canvasRef} className="h-[60vh] w-full max-w-xl" />
  </div>
</Track>

Engine: npm i scrollvars, 12.9 KB gzip as ESM (this page's fx/sv.js IIFE: 13.1 KB). All effects respect prefers-reduced-motion and render complete without JS.