Skip to main content
Three.js is a powerful 3D graphics library built on WebGL. Helios provides precise frame control for rendering Three.js scenes to video.

Basic setup

Three.js requires manual rendering driven by Helios instead of using requestAnimationFrame.
import * as THREE from 'three';
import { Helios } from '@helios-project/core';

// Setup Three.js
const canvas = document.getElementById('composition-canvas') as HTMLCanvasElement;
if (!canvas) throw new Error('Canvas not found');

const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
const scene = new THREE.Scene();

// Camera
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);
camera.position.z = 5;

// Lighting
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(1, 1, 1).normalize();
scene.add(light);

const ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);

// Object
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshPhongMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

// Resize Logic
const onResize = () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
};

window.addEventListener('resize', onResize);
onResize();

// Setup Helios
const helios = new Helios({ duration: 5, fps: 30 });
helios.bindToDocumentTimeline();

// Drive Animation
function draw(frame: number) {
  const time = frame / 30; // fps hardcoded to match Helios config

  // Rotate cube based on time
  cube.rotation.x = time;
  cube.rotation.y = time;

  renderer.render(scene, camera);
}

helios.subscribe((state) => {
  draw(state.currentFrame);
});

// Initial draw
draw(0);

// Expose for debugging
(window as any).helios = helios;
(window as any).scene = scene;

React Three Fiber integration

For React applications, use @react-three/fiber with Helios controlling the frame loop.
import React, { useState, useEffect } from 'react';
import { Canvas, RootState } from '@react-three/fiber';
import { Helios } from '@helios-project/core';
import Scene from './Scene';

// Singleton Helios instance
const helios = new Helios({
  fps: 30,
  duration: 10,
  autoSyncAnimations: true
});

helios.bindToDocumentTimeline();

if (typeof window !== 'undefined') {
  (window as any).helios = helios;
}

export default function App() {
  const [r3fState, setR3fState] = useState<RootState | null>(null);

  useEffect(() => {
    if (!r3fState) return;

    // Drive R3F loop manually
    return helios.subscribe((state) => {
      // R3F's advance(timestamp) sets the internal clock.elapsedTime
      // We pass time in seconds because Three.js clocks work in seconds
      const timeInSeconds = state.currentFrame / state.fps;

      // Explicitly advance the R3F state
      r3fState.advance(timeInSeconds);
    });
  }, [r3fState]);

  return (
    <Canvas
      frameloop="never"
      onCreated={(state) => setR3fState(state)}
      style={{ width: '100%', height: '100%', background: '#111' }}
      camera={{ position: [0, 0, 5] }}
    >
      <ambientLight intensity={0.5} />
      <pointLight position={[10, 10, 10]} />
      <Scene />
    </Canvas>
  );
}

Scene component

import React, { useRef } from 'react';
import { useFrame } from '@react-three/fiber';
import { Mesh } from 'three';

export default function Scene() {
  const meshRef = useRef<Mesh>(null);

  useFrame((state) => {
    // state.clock.elapsedTime matches what we passed to advance()
    const t = state.clock.elapsedTime;
    if (meshRef.current) {
      meshRef.current.rotation.x = t;
      meshRef.current.rotation.y = t * 0.5;
    }
  });

  return (
    <mesh ref={meshRef}>
      <boxGeometry args={[2, 2, 2]} />
      <meshStandardMaterial color="orange" />
    </mesh>
  );
}

Critical patterns

Disable automatic rendering

Three.js and React Three Fiber both have built-in render loops. You must disable them:
  • Vanilla Three.js: Don’t call requestAnimationFrame or renderer.setAnimationLoop()
  • React Three Fiber: Set frameloop="never" on the <Canvas> component

Use Helios time, not system time

Always calculate animations from state.currentFrame / fps instead of Date.now() or performance.now():
helios.subscribe((state) => {
  const time = state.currentFrame / state.fps; // Deterministic
  cube.rotation.x = time; // Repeatable across renders
});

Handle resize properly

Update camera aspect ratio and renderer size on window resize:
const onResize = () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
};

window.addEventListener('resize', onResize);

Performance considerations

WebGL context limits

Browsers limit the number of WebGL contexts. Destroy renderers when unmounting:
useEffect(() => {
  const renderer = new THREE.WebGLRenderer();
  
  return () => {
    renderer.dispose();
    // Also dispose geometries and materials
    geometry.dispose();
    material.dispose();
  };
}, []);

Scene complexity

Three.js performance depends on:
  • Polygon count: Keep meshes under 100k triangles for smooth rendering
  • Draw calls: Merge geometries to reduce draw calls
  • Texture size: Use power-of-2 textures (512x512, 1024x1024) for GPU efficiency
  • Lighting: Limit real-time shadows and use baked lighting where possible

Rendering resolution

Match the renderer size to your target export resolution:
// For 1080p export
renderer.setSize(1920, 1080);

// For 4K export
renderer.setSize(3840, 2160);
Higher resolutions increase render time proportionally.

Frame rate vs quality

Lower FPS reduces total render time but may affect motion smoothness:
  • 30 fps: Standard for most content, good balance
  • 60 fps: Smooth motion, doubles render time
  • 24 fps: Cinematic feel, fastest render

Package dependencies

{
  "dependencies": {
    "three": "^0.182.0",
    "@helios-project/core": "latest"
  },
  "devDependencies": {
    "typescript": "^5.0.0",
    "vite": "^7.1.2"
  }
}
For React Three Fiber:
{
  "dependencies": {
    "three": "^0.182.0",
    "@react-three/fiber": "^8.0.0",
    "@helios-project/core": "latest",
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  }
}

Common issues

Black screen on render

Ensure you have lighting in your scene:
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(1, 1, 1);
scene.add(light);

const ambient = new THREE.AmbientLight(0x404040);
scene.add(ambient);

Animation not playing

Verify Helios is bound to the document timeline:
helios.bindToDocumentTimeline();
And that you’re subscribing to state changes:
helios.subscribe((state) => {
  // Update scene based on state.currentFrame
});

Canvas not resizing

Update both camera and renderer on resize events:
window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});

Build docs developers (and LLMs) love