Skip to main content
Sequencing functions help you organize and coordinate multiple animations across your composition’s timeline. These utilities make it easy to create complex choreography without manual frame calculations.

sequence()

Calculates the local time and progress of a sequence relative to a start frame.
import { sequence } from '@helios-project/core';

const seq = sequence({
  frame: currentFrame,
  from: 30,
  durationInFrames: 60
});

if (seq.isActive) {
  const opacity = seq.progress;
}

Parameters

options
SequenceOptions
required
Sequence configuration object

Returns

SequenceResult object with the following properties:
localFrame
number
Frame number relative to the sequence start (frame - from)
relativeFrame
number
Alias for localFrame, for clarity
progress
number
Normalized progress from 0 to 1. Always 0 if no duration specified.
isActive
boolean
True if the current frame is within the sequence’s time range

Examples

Basic sequence

import { sequence } from '@helios-project/core';

function SequencedElement({ frame }) {
  const seq = sequence({
    frame,
    from: 30,
    durationInFrames: 60
  });
  
  if (!seq.isActive) return null;
  
  return (
    <div style={{ opacity: seq.progress }}>
      Visible from frame 30-90
    </div>
  );
}

Using local frame

const seq = sequence({ frame, from: 30, durationInFrames: 90 });

if (seq.isActive) {
  // seq.localFrame goes from 0 to 89
  const x = interpolate(seq.localFrame, [0, 90], [0, 800]);
}

Infinite sequence

// Sequence starts at frame 60 and runs until end of composition
const seq = sequence({ frame, from: 60 });

if (seq.isActive) {
  // Active from frame 60 onward
}

series()

Arranges items sequentially, automatically calculating start times based on each item’s duration.
import { series } from '@helios-project/core';

const scenes = series([
  { id: 'intro', durationInFrames: 60 },
  { id: 'main', durationInFrames: 120 },
  { id: 'outro', durationInFrames: 60 }
]);

// scenes[0].from = 0
// scenes[1].from = 60
// scenes[2].from = 180

Parameters

items
T[]
required
Array of items with durationInFrames and optional offset properties
startFrame
number
default:"0"
Starting frame for the first item in the series

Returns

Array<T & { from: number }> - Original items with from property added

Examples

Sequential scenes

import { series, sequence } from '@helios-project/core';

const scenes = series([
  { name: 'Intro', durationInFrames: 90 },
  { name: 'Main', durationInFrames: 180 },
  { name: 'Credits', durationInFrames: 60 }
]);

function VideoComposition({ frame }) {
  return (
    <>
      {scenes.map((scene) => {
        const seq = sequence({
          frame,
          from: scene.from,
          durationInFrames: scene.durationInFrames
        });
        
        if (!seq.isActive) return null;
        
        return <Scene key={scene.name} name={scene.name} progress={seq.progress} />;
      })}
    </>
  );
}

With offsets

const scenes = series([
  { id: 'a', durationInFrames: 60 },
  { id: 'b', durationInFrames: 60, offset: -10 }, // Starts 10 frames before 'a' ends
  { id: 'c', durationInFrames: 60, offset: 20 }   // Starts 20 frames after 'b' ends
]);

// scenes[0].from = 0
// scenes[1].from = 50  (60 - 10)
// scenes[2].from = 130 (50 + 60 + 20)

Custom start frame

const delayedScenes = series(
  [
    { durationInFrames: 60 },
    { durationInFrames: 90 }
  ],
  30 // Start at frame 30
);

// delayedScenes[0].from = 30
// delayedScenes[1].from = 90

stagger()

Staggers items at a fixed interval, useful for animating lists and grids.
import { stagger } from '@helios-project/core';

const items = stagger(
  ['Apple', 'Banana', 'Cherry'],
  10  // 10 frames between each
);

// items[0].from = 0
// items[1].from = 10
// items[2].from = 20

Parameters

items
T[]
required
Array of items to stagger
interval
number
required
Number of frames between each item’s start time
startFrame
number
default:"0"
Frame to start the first item at

Returns

Array<T & { from: number }> - Items with from property added

Examples

Staggered list animation

import { stagger, interpolate } from '@helios-project/core';

function StaggeredList({ items, frame }) {
  const staggeredItems = stagger(items, 8); // 8 frames delay between each
  
  return (
    <div>
      {staggeredItems.map((item, i) => {
        const opacity = interpolate(
          frame,
          [item.from, item.from + 20],
          [0, 1],
          { extrapolateRight: 'clamp' }
        );
        
        const y = interpolate(
          frame,
          [item.from, item.from + 20],
          [20, 0],
          { extrapolateRight: 'clamp' }
        );
        
        return (
          <div key={i} style={{ opacity, transform: `translateY(${y}px)` }}>
            {item}
          </div>
        );
      })}
    </div>
  );
}

Grid stagger

const grid = [
  ['A', 'B', 'C'],
  ['D', 'E', 'F'],
  ['G', 'H', 'I']
];

const flatGrid = grid.flat();
const staggeredGrid = stagger(flatGrid, 5, 30); // Start at frame 30

shift()

Shifts the start times of all items by a fixed offset.
import { series, shift } from '@helios-project/core';

const scenes = series([
  { durationInFrames: 60 },
  { durationInFrames: 90 }
]);

const delayedScenes = shift(scenes, 30); // Delay everything by 30 frames

Parameters

items
T[]
required
Array of items with a from property
offset
number
required
Number of frames to shift by (can be negative)

Returns

T[] - Items with updated from values

Examples

Delaying a sequence

const scenes = series([
  { durationInFrames: 60 },
  { durationInFrames: 90 }
]);

const delayed = shift(scenes, 60); // Start 60 frames later

Creating variations

const baseSequence = stagger(['A', 'B', 'C'], 10);

const earlyVersion = shift(baseSequence, -20);
const lateVersion = shift(baseSequence, 40);

Common patterns

Chapter-based composition

import { series, sequence } from '@helios-project/core';

const chapters = series([
  { title: 'Opening', durationInFrames: 90 },
  { title: 'Presentation', durationInFrames: 240 },
  { title: 'Demonstration', durationInFrames: 180 },
  { title: 'Conclusion', durationInFrames: 90 }
]);

function VideoComposition({ frame }) {
  return (
    <>
      {chapters.map((chapter) => {
        const seq = sequence({
          frame,
          from: chapter.from,
          durationInFrames: chapter.durationInFrames
        });
        
        return seq.isActive ? (
          <Chapter key={chapter.title} {...chapter} progress={seq.progress} />
        ) : null;
      })}
    </>
  );
}

Overlapping transitions

const scenes = series([
  { id: 'a', durationInFrames: 90 },
  { id: 'b', durationInFrames: 90, offset: -15 }, // 15 frame overlap
  { id: 'c', durationInFrames: 90, offset: -15 }
]);

function Scene({ scene, frame }) {
  const seq = sequence({
    frame,
    from: scene.from,
    durationInFrames: scene.durationInFrames
  });
  
  // Fade in during first 15 frames, fade out during last 15 frames
  let opacity = 1;
  if (seq.localFrame < 15) {
    opacity = seq.localFrame / 15;
  } else if (seq.localFrame > scene.durationInFrames - 15) {
    opacity = (scene.durationInFrames - seq.localFrame) / 15;
  }
  
  return <div style={{ opacity }}>Scene content</div>;
}

Multi-track timeline

const videoTrack = series([
  { type: 'clip', source: 'intro.mp4', durationInFrames: 120 },
  { type: 'clip', source: 'main.mp4', durationInFrames: 300 }
]);

const overlayTrack = stagger(
  ['Title', 'Subtitle', 'Call to action'],
  60,
  30 // Start at frame 30
);

function Timeline({ frame }) {
  return (
    <>
      <VideoLayer clips={videoTrack} frame={frame} />
      <OverlayLayer items={overlayTrack} frame={frame} />
    </>
  );
}

Combining with animation helpers

import { series, sequence, interpolate, spring, Easing } from '@helios-project/core';

const elements = stagger(
  ['First', 'Second', 'Third', 'Fourth'],
  12
);

function AnimatedElements({ frame }) {
  return elements.map((item, i) => {
    const startFrame = item.from;
    
    // Use spring for natural entrance
    const scale = spring({
      frame: frame - startFrame,
      fps: 30,
      from: 0,
      to: 1,
      config: { stiffness: 150, damping: 12 }
    });
    
    // Use interpolate with easing for position
    const x = interpolate(
      frame,
      [startFrame, startFrame + 24],
      [-100, 0],
      { easing: Easing.cubic.out, extrapolateRight: 'clamp' }
    );
    
    const opacity = frame >= startFrame ? 1 : 0;
    
    return (
      <div
        key={i}
        style={{
          opacity,
          transform: `translateX(${x}px) scale(${scale})`
        }}
      >
        {item}
      </div>
    );
  });
}

Build docs developers (and LLMs) love