Skip to main content

3D

FrameScript provides WebGL helpers and a Three.js canvas wrapper that survives context loss. Render mode waits for recovery so incomplete frames are avoided.

WebGL helpers

  • useWebGLContext initializes WebGL, handles webglcontextlost, and recreates resources.
  • useWebGLFrameWaiter waits for GPU completion per frame during headless rendering.
import { useRef } from "react"
import { useWebGLContext, useWebGLFrameWaiter } from "../src/lib/webgl"

const Canvas = () => {
const canvasRef = useRef<HTMLCanvasElement | null>(null)
const { glRef } = useWebGLContext(canvasRef, ({ gl }) => {
// init shaders/buffers
return () => {
// dispose resources
}
})

useWebGLFrameWaiter(glRef)

return <canvas ref={canvasRef} />
}

ThreeCanvas

<ThreeCanvas /> integrates with useAnimation by sampling variables in the update callback.

import { useAnimation, useVariable } from "../src/lib/animation"
import { BEZIER_SMOOTH } from "../src/lib/animation/functions"
import { seconds } from "../src/lib/frame"
import { ThreeCanvas, THREE, disposeThreeObject } from "../src/lib/webgl/three"

const Scene = () => {
const progress = useVariable(0)

useAnimation(async (ctx) => {
await ctx.move(progress).to(1, seconds(2), BEZIER_SMOOTH)
await ctx.move(progress).to(0, seconds(2), BEZIER_SMOOTH)
}, [])

return (
<ThreeCanvas
setup={({ renderer, size }) => {
renderer.outputColorSpace = THREE.SRGBColorSpace

const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(45, size.cssWidth / size.cssHeight, 0.1, 100)
camera.position.z = 6

const mesh = new THREE.Mesh(
new THREE.BoxGeometry(),
new THREE.MeshStandardMaterial({ color: 0x44aa88 })
)
scene.add(mesh)

const light = new THREE.DirectionalLight(0xffffff, 1)
light.position.set(2, 3, 4)
scene.add(light)

return {
scene,
camera,
update: ({ frame }) => {
const t = progress.get(frame)
mesh.position.x = (t - 0.5) * 3
mesh.rotation.y = t * Math.PI * 2
},
dispose: () => disposeThreeObject(mesh),
}
}}
/>
)
}