Skip to main content
Helios works perfectly with vanilla JavaScript, giving you complete control without framework overhead.

Quick start

1

Install Helios

npm install @helios-project/core
2

Create your first animation

main.ts
import { Helios } from '@helios-project/core';
import './style.css';

// Initialize Helios engine
const helios = new Helios({
    duration: 5,
    fps: 30,
    autoSyncAnimations: true
});

// Expose for the Player and debugging
(window as any).helios = helios;

// Enable external control (e.g. from Renderer)
helios.bindToDocumentTimeline();
3

Add animation logic

// Subscribe to frame updates
helios.subscribe((state) => {
    const { currentFrame } = state;
    const progress = currentFrame / (helios.duration * helios.fps);

    // Update DOM elements
    const box = document.getElementById('box');
    if (box) {
        box.style.transform = `translateX(${progress * 500}px) rotate(${currentFrame * 2}deg)`;
        box.style.opacity = String(Math.min(1, currentFrame / 30));
    }
});

Animation approaches

Directly manipulate DOM elements in the subscribe callback:
import { Helios } from '@helios-project/core';

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

const box = document.createElement('div');
box.style.width = '100px';
box.style.height = '100px';
box.style.backgroundColor = 'red';
document.body.appendChild(box);

helios.subscribe((state) => {
    const progress = state.currentFrame / (state.duration * state.fps);
    
    // Animate position
    const x = progress * 500;
    box.style.transform = `translateX(${x}px)`;
    
    // Animate opacity
    box.style.opacity = String(Math.min(1, state.currentFrame / 30));
});

(window as any).helios = helios;

Animation helpers

Use Helios utility functions for common animation patterns:
import { Helios, interpolate, spring } from '@helios-project/core';

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

const helperBox = document.createElement('div');
helperBox.className = 'box';
helperBox.style.backgroundColor = 'hotpink';
helperBox.style.position = 'absolute';
helperBox.style.top = '250px';
helperBox.style.left = '50px';
helperBox.style.width = '100px';
helperBox.style.height = '100px';
document.body.appendChild(helperBox);

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

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

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

    helperBox.style.transform = `translateX(${x}px) scale(${scale})`;
});

helios.bindToDocumentTimeline();
(window as any).helios = helios;

Manual sequencing

Implement sequences with conditional logic:
import { Helios } from '@helios-project/core';

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

const app = document.getElementById('app')!;

// Create sequence boxes
function createBox(color: string, text: string) {
    const box = document.createElement('div');
    box.className = 'box';
    box.style.backgroundColor = color;
    box.textContent = text;
    box.style.position = 'absolute';
    box.style.display = 'none';
    box.style.width = '100px';
    box.style.height = '100px';
    return box;
}

const seq1 = createBox('red', 'Seq 1');
app.appendChild(seq1);

const seq2 = createBox('blue', 'Seq 2');
app.appendChild(seq2);

const seq3 = createBox('green', 'Seq 3');
app.appendChild(seq3);

// Subscribe to updates
helios.subscribe((state) => {
    const { currentFrame } = state;

    // Sequence 1: 0 - 30
    if (currentFrame >= 0 && currentFrame < 30) {
        seq1.style.display = 'flex';
    } else {
        seq1.style.display = 'none';
    }

    // Sequence 2: 30 - 60
    if (currentFrame >= 30 && currentFrame < 60) {
        seq2.style.display = 'flex';
    } else {
        seq2.style.display = 'none';
    }

    // Sequence 3: 60 - 90
    if (currentFrame >= 60 && currentFrame < 90) {
        seq3.style.display = 'flex';
    } else {
        seq3.style.display = 'none';
    }
});

helios.bindToDocumentTimeline();
(window as any).helios = helios;

Advanced patterns

Multiple animations

Orchestrate multiple elements:
import { Helios, interpolate } from '@helios-project/core';

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

const elements = [
    { el: document.getElementById('box1')!, delay: 0, color: 'red' },
    { el: document.getElementById('box2')!, delay: 15, color: 'blue' },
    { el: document.getElementById('box3')!, delay: 30, color: 'green' }
];

helios.subscribe((state) => {
    elements.forEach(({ el, delay }) => {
        const localFrame = Math.max(0, state.currentFrame - delay);
        const x = interpolate(localFrame, [0, 60], [0, 400], { extrapolateRight: 'clamp' });
        el.style.transform = `translateX(${x}px)`;
    });
});

helios.bindToDocumentTimeline();

State machines

Implement complex animation states:
import { Helios } from '@helios-project/core';

enum AnimationState {
    INTRO = 'intro',
    MAIN = 'main',
    OUTRO = 'outro'
}

const helios = new Helios({ duration: 5, fps: 30 });
let currentState: AnimationState = AnimationState.INTRO;

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

    // Determine state
    if (currentFrame < 30) {
        currentState = AnimationState.INTRO;
    } else if (currentFrame < 120) {
        currentState = AnimationState.MAIN;
    } else {
        currentState = AnimationState.OUTRO;
    }

    // Execute state-specific logic
    switch (currentState) {
        case AnimationState.INTRO:
            // Intro animations
            break;
        case AnimationState.MAIN:
            // Main animations
            break;
        case AnimationState.OUTRO:
            // Outro animations
            break;
    }
});

helios.bindToDocumentTimeline();

Best practices

Query DOM elements once, not in every frame:
// Good: Query once
const box = document.getElementById('box');
helios.subscribe((state) => {
    if (box) {
        box.style.transform = `translateX(${state.currentFrame * 5}px)`;
    }
});

// Avoid: Query every frame
helios.subscribe((state) => {
    const box = document.getElementById('box'); // ❌
    // ...
});
For interactive animations, combine with requestAnimationFrame:
let latestState = helios.getState();

helios.subscribe((state) => {
    latestState = state;
});

function render() {
    // Use latestState for rendering
    const progress = latestState.currentFrame / (helios.duration * helios.fps);
    // Update DOM...
    requestAnimationFrame(render);
}

requestAnimationFrame(render);
Enable external control for rendering and playback:
const helios = new Helios({ duration: 5, fps: 30 });
helios.bindToDocumentTimeline();
(window as any).helios = helios;
Unsubscribe when removing animations:
const unsubscribe = helios.subscribe((state) => {
    // Animation logic
});

// Later, when cleaning up:
unsubscribe();

TypeScript support

Helios is built with TypeScript and provides full type safety:
import type { Helios, HeliosState } from '@helios-project/core';

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

helios.subscribe((state: HeliosState) => {
    const { currentFrame, duration, fps } = state;
    // Type-safe state access
});

Next steps

Animation helpers

Learn about interpolation, spring physics, and easing functions

Canvas rendering

Create high-performance canvas animations

Sequences

Build complex multi-scene animations

Export videos

Render your animations to video files

Build docs developers (and LLMs) love