Three.js scene on the pin
A WebGL scene scrubbed by scroll. The canvas harness runs the lifecycle, Three renders.
<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>