> ## Documentation Index
> Fetch the complete documentation index at: https://docs.heliosrender.com/llms.txt
> Use this file to discover all available pages before exploring further.

# React integration

> Use Helios with React for declarative video animations

Helios provides first-class support for React through hooks and components that integrate seamlessly with React's declarative programming model.

## Quick start

**1. Install dependencies**

```bash theme={null}
npm install @helios-project/core react react-dom
```

**2. Create the useVideoFrame hook**

```tsx hooks/useVideoFrame.ts theme={null}
import { useState, useEffect } from 'react';
import { Helios } from '@helios-project/core';

export function useVideoFrame(helios: Helios) {
    const [frame, setFrame] = useState(helios.getState().currentFrame);

    useEffect(() => {
        // Update local state when helios state changes
        const update = (state: { currentFrame: number }) => setFrame(state.currentFrame);

        // Subscribe returns an unsubscribe function
        return helios.subscribe(update);
    }, [helios]);

    return frame;
}
```

**3. Create your first animation**

```tsx App.tsx theme={null}
import { Helios } from '@helios-project/core';
import { useVideoFrame } from './hooks/useVideoFrame';

const duration = 5;
const fps = 30;
const helios = new Helios({ duration, fps });

// Bind to document timeline so it can be driven by the player/renderer
helios.bindToDocumentTimeline();

// Expose to window for debugging/player control
if (typeof window !== 'undefined') {
    (window as any).helios = helios;
}

export default function App() {
    const frame = useVideoFrame(helios);

    // Calculate animation values
    const progress = frame / (duration * fps);
    const opacity = Math.min(1, frame / 30);
    const scale = 1 + Math.sin(progress * Math.PI * 4) * 0.2;
    const rotation = progress * 360;

    return (
        <div style={{
            display: 'flex',
            flexDirection: 'column',
            alignItems: 'center',
            justifyContent: 'center',
            height: '100%',
            color: 'white',
            fontFamily: 'sans-serif'
        }}>
            <div style={{
                width: 200,
                height: 200,
                backgroundColor: '#61dafb',
                borderRadius: 20,
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center',
                opacity: opacity,
                transform: `scale(${scale}) rotate(${rotation}deg)`,
                boxShadow: '0 0 20px rgba(97, 218, 251, 0.5)'
            }}>
                <span style={{
                    fontSize: '2rem',
                    fontWeight: 'bold',
                    color: '#282c34',
                    transform: `rotate(-${rotation}deg)`
                }}>
                    React
                </span>
            </div>

            <div style={{ marginTop: 40, textAlign: 'center' }}>
                <p>Frame: {frame.toFixed(2)}</p>
                <p>Progress: {(progress * 100).toFixed(1)}%</p>
                <p>FPS: {fps}</p>
            </div>
        </div>
    );
}
```

## Animation approaches

> [tab] DOM animations
>
> Animate React components using inline styles and the `useVideoFrame` hook:
>
> ```tsx theme={null}
> import { useVideoFrame } from './hooks/useVideoFrame';
>
> export default function DOMAnimation() {
>     const frame = useVideoFrame(helios);
>     const progress = frame / (duration * fps);
>
>     return (
>         <div style={{
>             opacity: Math.min(1, frame / 30),
>             transform: `translateX(${progress * 500}px) rotate(${frame * 2}deg)`
>         }}>
>             Animated content
>         </div>
>     );
> }
> ```

> [tab] Canvas animations
>
> Use refs and `useEffect` to draw frame-by-frame animations:
>
> ```tsx theme={null}
> import { useRef, useEffect } from 'react';
> import { useVideoFrame } from './hooks/useVideoFrame';
>
> export default function CanvasAnimation() {
>     const canvasRef = useRef<HTMLCanvasElement>(null);
>     const frame = useVideoFrame(helios);
>
>     useEffect(() => {
>         const canvas = canvasRef.current;
>         if (!canvas) return;
>
>         const ctx = canvas.getContext('2d');
>         if (!ctx) return;
>
>         const { width, height } = canvas;
>         const progress = frame / (duration * fps);
>
>         // Clear
>         ctx.fillStyle = '#111';
>         ctx.fillRect(0, 0, width, height);
>
>         // Draw animated shapes
>         const cx = width / 2;
>         const cy = height / 2;
>         const radius = 100;
>
>         ctx.save();
>         ctx.translate(cx, cy);
>         ctx.rotate(progress * Math.PI * 2);
>
>         ctx.strokeStyle = '#61dafb';
>         ctx.lineWidth = 5;
>
>         // Atom rings
>         for (let i = 0; i < 3; i++) {
>             ctx.save();
>             ctx.rotate((i * Math.PI) / 3);
>             ctx.beginPath();
>             ctx.ellipse(0, 0, radius, radius / 2.5, 0, 0, Math.PI * 2);
>             ctx.stroke();
>             ctx.restore();
>         }
>
>         // Nucleus
>         ctx.fillStyle = '#61dafb';
>         ctx.beginPath();
>         ctx.arc(0, 0, 20, 0, Math.PI * 2);
>         ctx.fill();
>
>         ctx.restore();
>     }, [frame]);
>
>     return (
>         <canvas
>             ref={canvasRef}
>             width={1920}
>             height={1080}
>             style={{ width: '100%', height: '100%' }}
>         />
>     );
> }
> ```

## Animation helpers

Helios provides utility functions for common animation patterns:

```tsx theme={null}
import { interpolate, spring } from '@helios-project/core';
import { useVideoFrame } from './hooks/useVideoFrame';

function HelperDemo() {
    const frame = useVideoFrame(helios);

    // Interpolate x position: 0 -> 200 over frames 0-60
    const x = interpolate(frame, [0, 60], [0, 200], { extrapolateRight: 'clamp' });

    // Spring scale: 0 -> 1 starting at frame 0
    const scale = spring({ 
        frame, 
        fps: 30, 
        from: 0, 
        to: 1, 
        config: { stiffness: 100 } 
    });

    return (
        <div style={{
            transform: `translateX(${x}px) scale(${scale})`,
            width: 100,
            height: 100,
            background: 'hotpink'
        }}>
            Animated
        </div>
    );
}
```

## Sequencing components

### Sequence component

Create time-based sequences that show/hide content:

```tsx components/Sequence.tsx theme={null}
import { useContext } from 'react';
import { sequence } from '@helios-project/core';
import { FrameContext } from './FrameContext';

export const Sequence = ({ from, durationInFrames, children }) => {
    const parentFrame = useContext(FrameContext);
    const { isActive, relativeFrame } = sequence({ 
        frame: parentFrame, 
        from, 
        durationInFrames 
    });

    if (!isActive) return null;

    return (
        <FrameContext.Provider value={relativeFrame}>
            {children}
        </FrameContext.Provider>
    );
};
```

### Series component

Automatically sequence child components one after another:

```tsx components/Series.tsx theme={null}
import { Children, cloneElement, isValidElement } from 'react';

export const Series = ({ children }) => {
    let currentFrom = 0;

    return (
        <>
            {Children.map(children, (child) => {
                if (!isValidElement(child)) {
                    return child;
                }

                const duration = child.props.durationInFrames || 0;
                const newProps = { from: currentFrom };
                currentFrom += duration;

                return cloneElement(child, newProps);
            })}
        </>
    );
};
```

### Frame context

Create a context for sharing frame state:

```tsx components/FrameContext.tsx theme={null}
import { createContext } from 'react';

export const FrameContext = createContext(0);
```

### Usage example

```tsx theme={null}
import { useState, useEffect } from 'react';
import { FrameContext } from './components/FrameContext';
import { Sequence } from './components/Sequence';
import { Series } from './components/Series';

function MovingBox({ color, label }) {
    const frame = useContext(FrameContext);
    const x = frame * 5;

    return (
        <div style={{
            position: 'absolute',
            left: `${x}px`,
            width: 100,
            height: 100,
            backgroundColor: color
        }}>
            {label}
        </div>
    );
}

export default function App() {
    const [frame, setFrame] = useState(0);

    useEffect(() => {
        return helios.subscribe((state) => setFrame(state.currentFrame));
    }, []);

    return (
        <FrameContext.Provider value={frame}>
            <Series>
                <Sequence durationInFrames={60}>
                    <MovingBox color="#ff4444" label="Seq 1" />
                </Sequence>

                <Sequence durationInFrames={60}>
                    <MovingBox color="#4444ff" label="Seq 2" />
                </Sequence>
            </Series>
        </FrameContext.Provider>
    );
}
```

## Best practices

> [accordion] Initialize Helios outside components
>
> Create the Helios instance outside your component to avoid recreating it on every render:
>
> ```tsx theme={null}
> // Good: Single instance
> const helios = new Helios({ duration: 5, fps: 30 });
> helios.bindToDocumentTimeline();
>
> export default function App() {
>     const frame = useVideoFrame(helios);
>     // ...
> }
> ```
>
> ```tsx theme={null}
> // Avoid: New instance on every render
> export default function App() {
>     const helios = new Helios({ duration: 5, fps: 30 }); // ❌
>     // ...
> }
> ```

> [accordion] Bind to document timeline
>
> Always call `bindToDocumentTimeline()` to enable external control by the player or renderer:
>
> ```tsx theme={null}
> const helios = new Helios({ duration: 5, fps: 30 });
> helios.bindToDocumentTimeline();
> ```

> [accordion] Expose helios for debugging
>
> Attach the Helios instance to `window` for easy debugging and player integration:
>
> ```tsx theme={null}
> if (typeof window !== 'undefined') {
>     (window as any).helios = helios;
> }
> ```

> [accordion] Use strict mode carefully
>
> React 18+ Strict Mode causes double-mounting in development. This is fine for Helios, but be aware of potential duplicate subscriptions during development.

## TypeScript support

Helios is written in TypeScript and provides full type definitions:

```tsx theme={null}
import type { Helios } from '@helios-project/core';

interface VideoFrameHookReturn {
    frame: number;
    progress: number;
}

export function useVideoFrame(helios: Helios): number {
    const [frame, setFrame] = useState<number>(helios.getState().currentFrame);
    // ...
    return frame;
}
```

## Next steps

> [card] [Animation helpers](https://docs.heliosrender.com/features/animation-helpers.md)
>
> Learn about interpolation, spring physics, and easing functions

> [card] [Canvas rendering](https://docs.heliosrender.com/guides/canvas)
>
> Create high-performance canvas animations

> [card] [Sequences](https://docs.heliosrender.com/guides/sequences)
>
> Build complex multi-scene animations

> [card] [Export videos](https://docs.heliosrender.com/guides/export)
>
> Render your animations to video files


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.