Documentation Index
Fetch the complete documentation index at: https://mintlify.com/BintzGavin/helios/llms.txt
Use this file to discover all available pages before exploring further.
Helios drives the browser’s native animation engine rather than reimplementing animation in JavaScript. This means your existing CSS animations, WAAPI timelines, and animation libraries work out of the box.
CSS animations
Standard CSS @keyframes animations work natively with Helios when autoSyncAnimations is enabled.
Basic CSS animation
@keyframes fadeIn {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
.title {
animation: fadeIn 2s ease-out forwards;
}
import { Helios } from '@helios-project/core';
const helios = new Helios({
duration: 5,
fps: 30,
autoSyncAnimations: true // Enable CSS animation sync
});
window.helios = helios;
helios.bindToDocumentTimeline();
With autoSyncAnimations: true, Helios automatically:
- Discovers all CSS animations via
document.getAnimations()
- Sets
animation.currentTime to match the timeline
- Pauses animations to prevent drift
See packages/core/src/drivers/DomDriver.ts:372 for the WAAPI sync implementation.
Animation timing
CSS animation duration maps directly to composition time:
/* 3-second animation */
.element {
animation: slideIn 3s ease-out forwards;
}
At 30fps:
- Frame 0: Animation at 0% (start state)
- Frame 45: Animation at 50% (1.5 seconds)
- Frame 90: Animation at 100% (end state)
Animation delay
.element {
animation: fadeIn 2s ease-out 1s forwards;
/* ↑ 1 second delay */
}
Animation won’t start until 1 second into the composition (frame 30 at 30fps).
Multiple animations
.element {
animation:
fadeIn 1s ease-out forwards,
slideUp 1s ease-out forwards,
rotate 2s linear infinite;
}
All animations are synchronized to the same timeline.
Web Animations API (WAAPI)
The Web Animations API provides programmatic animation control while still using the browser’s rendering engine.
Creating WAAPI animations
const element = document.querySelector('.box');
const animation = element.animate(
[
{ transform: 'translateX(0px)', opacity: 0 },
{ transform: 'translateX(500px)', opacity: 1 }
],
{
duration: 2000, // 2 seconds
easing: 'ease-out',
fill: 'forwards'
}
);
// Helios automatically syncs this animation when autoSyncAnimations is enabled
Manual WAAPI control
Without autoSyncAnimations, you can manually control WAAPI animations:
const helios = new Helios({
duration: 5,
fps: 30,
autoSyncAnimations: false
});
const animation = element.animate([...], { duration: 2000 });
helios.currentTime.subscribe(time => {
animation.currentTime = time * 1000; // Convert to milliseconds
animation.pause();
});
Shadow DOM support
Helios automatically discovers animations in Shadow DOM when using DomDriver:
class MyComponent extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.inner {
animation: pulse 2s infinite;
}
</style>
<div class="inner">Pulsing element</div>
`;
}
}
customElements.define('my-component', MyComponent);
The DomDriver scans shadow roots automatically (see DomDriver.ts:120).
Animation libraries
Helios works with popular animation libraries that use WAAPI under the hood or provide time-based APIs.
GSAP
Time-based (recommended)
With autoSyncAnimations
import gsap from 'gsap';
import { Helios } from '@helios-project/core';
const helios = new Helios({ duration: 5, fps: 30 });
// Create timeline
const tl = gsap.timeline({ paused: true });
tl.to('.box', { x: 500, duration: 2 });
tl.to('.box', { rotation: 360, duration: 1 });
// Sync with Helios time
helios.currentTime.subscribe(time => {
tl.time(time);
});
import gsap from 'gsap';
import { Helios } from '@helios-project/core';
const helios = new Helios({
duration: 5,
fps: 30,
autoSyncAnimations: true // GSAP uses WAAPI internally
});
gsap.to('.box', {
x: 500,
duration: 2,
ease: 'power2.out'
});
// Helios will automatically sync this
See examples/gsap-animation/ for a complete example.
Framer Motion
import { motion } from 'framer-motion';
import { useVideoFrame } from '@helios-project/react';
function AnimatedBox() {
const { currentTime } = useVideoFrame();
return (
<motion.div
animate={{ x: 500, opacity: 1 }}
transition={{ duration: 2 }}
style={{ x: 0, opacity: 0 }}
/>
);
}
With Helios’s React adapter:
const helios = new Helios({
duration: 5,
fps: 30,
autoSyncAnimations: true
});
<HeliosProvider helios={helios}>
<AnimatedBox />
</HeliosProvider>
See examples/framer-motion-animation/ for details.
Motion One
import { animate } from 'motion';
import { Helios } from '@helios-project/core';
const helios = new Helios({
duration: 5,
fps: 30,
autoSyncAnimations: true
});
animate('.box', { x: 500 }, { duration: 2 });
// Automatically synced via WAAPI
See examples/motion-one-animation/.
Lottie
import lottie from 'lottie-web';
import { Helios } from '@helios-project/core';
const helios = new Helios({ duration: 5, fps: 30 });
const animation = lottie.loadAnimation({
container: document.getElementById('lottie'),
renderer: 'svg',
loop: false,
autoplay: false,
path: 'animation.json'
});
helios.currentTime.subscribe(time => {
const totalFrames = animation.totalFrames;
const duration = animation.getDuration();
const frame = (time / duration) * totalFrames;
animation.goToAndStop(frame, true);
});
See examples/lottie-animation/.
Animation helpers
Helios provides utility functions for common animation patterns.
Interpolate
Map input values to output values with easing:
import { interpolate } from '@helios-project/core';
helios.subscribe(({ currentFrame }) => {
// Fade in from frames 0-30
const opacity = interpolate(
currentFrame,
[0, 30],
[0, 1],
{ easing: t => t * t } // Ease-in quadratic
);
element.style.opacity = opacity;
});
Full interpolate API at packages/core/src/animation.ts:20:
function interpolate(
input: number,
inputRange: number[],
outputRange: number[],
options?: {
extrapolateLeft?: 'extend' | 'clamp' | 'identity',
extrapolateRight?: 'extend' | 'clamp' | 'identity',
easing?: (t: number) => number
}
): number
// Linear interpolation
const x = interpolate(frame, [0, 100], [0, 500]);
// frame 0 → x = 0
// frame 50 → x = 250
// frame 100 → x = 500
// Different speeds in different ranges
const y = interpolate(
frame,
[0, 30, 60, 90],
[0, 100, 150, 500]
);
// Slow: frames 0-30 → 0-100 (fast)
// Slow: frames 30-60 → 100-150 (slow)
// Fast: frames 60-90 → 150-500 (fast)
Spring
Physics-based spring animations:
import { spring } from '@helios-project/core';
helios.subscribe(({ currentFrame, fps }) => {
const y = spring({
frame: currentFrame,
fps,
from: 0,
to: 500,
config: {
stiffness: 100,
damping: 10,
mass: 1
}
});
element.style.transform = `translateY(${y}px)`;
});
Spring configurations:
// Bouncy
{ stiffness: 200, damping: 10 }
// Gentle
{ stiffness: 50, damping: 20 }
// Quick snap
{ stiffness: 300, damping: 30 }
See animation.ts:136 for physics implementation.
Calculate spring duration
import { calculateSpringDuration } from '@helios-project/core';
const frames = calculateSpringDuration(
{
fps: 30,
from: 0,
to: 500,
config: { stiffness: 100, damping: 10 }
},
0.001 // Threshold for "settled"
);
console.log(`Spring settles in ${frames} frames`);
Sequencing
Coordinate animations across time.
Sequence helper
import { sequence } from '@helios-project/core';
helios.subscribe(({ currentFrame }) => {
// Title animation: frames 0-60
const title = sequence({
frame: currentFrame,
from: 0,
durationInFrames: 60
});
if (title.isActive) {
const opacity = interpolate(
title.localFrame,
[0, 30],
[0, 1]
);
titleElement.style.opacity = opacity;
}
// Subtitle animation: frames 30-90 (overlaps title)
const subtitle = sequence({
frame: currentFrame,
from: 30,
durationInFrames: 60
});
if (subtitle.isActive) {
const y = interpolate(
subtitle.localFrame,
[0, 60],
[50, 0]
);
subtitleElement.style.transform = `translateY(${y}px)`;
}
});
Sequence returns:
interface SequenceResult {
localFrame: number; // Frame relative to start (0, 1, 2, ...)
relativeFrame: number; // Alias for localFrame
progress: number; // 0 to 1 progress through duration
isActive: boolean; // true if within duration
}
See packages/core/src/sequencing.ts:20 for implementation.
Series helper
Place items sequentially:
import { series } from '@helios-project/core';
const items = [
{ id: 'intro', durationInFrames: 60 },
{ id: 'main', durationInFrames: 120 },
{ id: 'outro', durationInFrames: 60 }
];
const sequenced = series(items);
// [
// { id: 'intro', durationInFrames: 60, from: 0 },
// { id: 'main', durationInFrames: 120, from: 60 },
// { id: 'outro', durationInFrames: 60, from: 180 }
// ]
helios.subscribe(({ currentFrame }) => {
sequenced.forEach(item => {
const seq = sequence({
frame: currentFrame,
from: item.from,
durationInFrames: item.durationInFrames
});
if (seq.isActive) {
console.log(`Showing ${item.id}`);
}
});
});
Stagger helper
Stagger animations by a fixed interval:
import { stagger } from '@helios-project/core';
const boxes = document.querySelectorAll('.box');
const staggered = stagger(
Array.from(boxes),
10, // 10 frame delay between each
30 // Start at frame 30
);
helios.subscribe(({ currentFrame }) => {
staggered.forEach((box, i) => {
const seq = sequence({
frame: currentFrame,
from: box.from,
durationInFrames: 30
});
if (seq.isActive) {
const y = interpolate(seq.localFrame, [0, 30], [50, 0]);
box.style.transform = `translateY(${y}px)`;
}
});
});
Canvas and procedural animation
For canvas-based compositions, you control the render loop:
import { Helios } from '@helios-project/core';
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const helios = new Helios({
duration: 5,
fps: 30,
width: 1920,
height: 1080
});
helios.subscribe(({ currentTime, currentFrame, width, height }) => {
// Clear canvas
ctx.clearRect(0, 0, width, height);
// Draw based on time
const x = (currentTime / 5) * width;
const radius = 50 + Math.sin(currentTime * Math.PI) * 20;
ctx.fillStyle = '#ff6b6b';
ctx.beginPath();
ctx.arc(x, height / 2, radius, 0, Math.PI * 2);
ctx.fill();
});
See examples/p5-canvas-animation/ and examples/pixi-canvas-animation/ for library integrations.
Easing functions
Helios provides common easing functions:
import { Easing } from '@helios-project/core';
// Available easings
Easing.linear
Easing.easeIn.quad
Easing.easeIn.cubic
Easing.easeOut.quad
Easing.easeOut.cubic
Easing.easeInOut.quad
Easing.easeInOut.cubic
// ... and more
// Use with interpolate
const x = interpolate(
currentFrame,
[0, 60],
[0, 500],
{ easing: Easing.easeOut.cubic }
);
See packages/core/src/easing.ts for the full list.
Best practices
Prefer CSS for simple animations
/* Good: Declarative, browser-optimized */
@keyframes slideIn {
from { transform: translateX(-100%); }
to { transform: translateX(0); }
}
.element {
animation: slideIn 1s ease-out forwards;
}
// Avoid: Imperative, per-frame calculation
helios.subscribe(({ currentFrame }) => {
const progress = currentFrame / 30;
element.style.transform = `translateX(${progress * -100}%)`;
});
.animated {
will-change: transform, opacity;
}
This hints to the browser to optimize for animation.
Avoid layout thrashing
// Bad: Reading and writing in a loop
elements.forEach(el => {
const width = el.offsetWidth; // Read (forces layout)
el.style.width = width + 10 + 'px'; // Write
});
// Good: Batch reads, then writes
const widths = elements.map(el => el.offsetWidth);
widths.forEach((width, i) => {
elements[i].style.width = width + 10 + 'px';
});
Use hardware-accelerated properties
Prefer transform and opacity over layout-affecting properties:
/* Fast: GPU-accelerated */
@keyframes slideIn {
from { transform: translateX(-100%); }
to { transform: translateX(0); }
}
/* Slow: Triggers layout */
@keyframes slideIn {
from { left: -100%; }
to { left: 0; }
}
Next steps