Skip to main content
Helios provides lightweight transition utilities for creating smooth fades, crossfades, and custom transitions between scenes or states.

transition()

Calculates transition progress over a specified duration with optional easing.
import { transition } from '@helios-project/core';

const progress = transition(frame, 30, 60); // 0 to 1 from frame 30 to 90

Parameters

frame
number
required
Current frame number
start
number
required
Frame to start the transition
duration
number
required
Duration of the transition in frames
options
TransitionOptions
Optional transition configuration

Returns

number - Progress value:
  • 0 before start frame
  • 0 to 1 during transition
  • 1 after transition completes

Examples

Basic fade in

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

function FadeIn({ frame }) {
  const opacity = transition(frame, 0, 30); // Fade in over 30 frames
  
  return (
    <div style={{ opacity }}>
      Content fades in
    </div>
  );
}

With easing

import { transition, Easing } from '@helios-project/core';

function SmoothFadeIn({ frame }) {
  const opacity = transition(frame, 0, 60, {
    easing: Easing.cubic.out
  });
  
  return <div style={{ opacity }}>Smooth fade</div>;
}

Delayed transition

// Start transition at frame 120, duration 45 frames
const opacity = transition(frame, 120, 45);

Scale transition

function ScaleIn({ frame }) {
  const progress = transition(frame, 30, 60, {
    easing: Easing.back.out // Slight overshoot
  });
  
  const scale = 0.5 + (progress * 0.5); // Scale from 0.5 to 1
  
  return (
    <div style={{ transform: `scale(${scale})` }}>
      Content
    </div>
  );
}

crossfade()

Calculates opacity values for crossfading between two elements.
import { crossfade } from '@helios-project/core';

const { in: opacityIn, out: opacityOut } = crossfade(frame, 60, 30);

Parameters

frame
number
required
Current frame number
start
number
required
Frame to start the crossfade
duration
number
required
Duration of the crossfade in frames
options
TransitionOptions
Optional transition configuration

Returns

CrossfadeResult object with:
in
number
Opacity for the incoming element (0 to 1)
out
number
Opacity for the outgoing element (1 to 0)

Examples

Basic crossfade between scenes

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

function SceneTransition({ frame }) {
  const fade = crossfade(frame, 90, 30); // 30-frame crossfade at frame 90
  
  return (
    <>
      <div style={{ opacity: fade.out }}>
        Scene A
      </div>
      <div style={{ opacity: fade.in }}>
        Scene B
      </div>
    </>
  );
}

Smooth crossfade with easing

import { crossfade, Easing } from '@helios-project/core';

function SmoothTransition({ frame }) {
  const fade = crossfade(frame, 60, 45, {
    easing: Easing.sine.inOut
  });
  
  return (
    <>
      <SceneA opacity={fade.out} />
      <SceneB opacity={fade.in} />
    </>
  );
}

Multiple overlapping scenes

function MultiSceneComposition({ frame }) {
  const fade1 = crossfade(frame, 90, 30);
  const fade2 = crossfade(frame, 180, 30);
  const fade3 = crossfade(frame, 270, 30);
  
  return (
    <>
      <Scene1 opacity={fade1.out} />
      <Scene2 opacity={Math.min(fade1.in, fade2.out)} />
      <Scene3 opacity={Math.min(fade2.in, fade3.out)} />
      <Scene4 opacity={fade3.in} />
    </>
  );
}

Common patterns

Scene manager with transitions

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

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

const TRANSITION_DURATION = 20;

function VideoComposition({ frame }) {
  return (
    <>
      {scenes.map((scene, i) => {
        const nextScene = scenes[i + 1];
        let opacity = 1;
        
        // Fade in at start
        if (frame < scene.from + TRANSITION_DURATION) {
          const fade = crossfade(
            frame,
            scene.from,
            TRANSITION_DURATION
          );
          opacity = fade.in;
        }
        
        // Fade out before next scene
        if (nextScene && frame > nextScene.from - TRANSITION_DURATION) {
          const fade = crossfade(
            frame,
            nextScene.from - TRANSITION_DURATION,
            TRANSITION_DURATION
          );
          opacity = fade.out;
        }
        
        return (
          <Scene key={scene.id} id={scene.id} opacity={opacity} />
        );
      })}
    </>
  );
}

Custom transition effects

import { transition, Easing } from '@helios-project/core';

function SlideTransition({ frame, children }) {
  const progress = transition(frame, 0, 60, {
    easing: Easing.cubic.out
  });
  
  const x = (1 - progress) * 100; // Slide from right
  const opacity = progress;
  
  return (
    <div style={{
      transform: `translateX(${x}%)`,
      opacity
    }}>
      {children}
    </div>
  );
}

function ZoomTransition({ frame, children }) {
  const progress = transition(frame, 30, 45, {
    easing: Easing.back.out
  });
  
  const scale = 0.3 + (progress * 0.7); // Scale from 0.3 to 1
  const opacity = progress;
  
  return (
    <div style={{
      transform: `scale(${scale})`,
      opacity
    }}>
      {children}
    </div>
  );
}

Transition with blur

function BlurTransition({ frame }) {
  const fade = crossfade(frame, 90, 30);
  
  // Blur increases during transition
  const blurOut = (1 - fade.out) * 10;
  const blurIn = (1 - fade.in) * 10;
  
  return (
    <>
      <div style={{
        opacity: fade.out,
        filter: `blur(${blurOut}px)`
      }}>
        Previous scene
      </div>
      <div style={{
        opacity: fade.in,
        filter: `blur(${blurIn}px)`
      }}>
        Next scene
      </div>
    </>
  );
}

Wipe transition

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

function WipeTransition({ frame, direction = 'left' }) {
  const progress = transition(frame, 60, 40);
  
  const clipPath = direction === 'left'
    ? `inset(0 ${(1 - progress) * 100}% 0 0)`
    : `inset(0 0 0 ${(1 - progress) * 100}%)`;
  
  return (
    <>
      <div>Background</div>
      <div style={{ clipPath }}>
        Foreground (wipes in)
      </div>
    </>
  );
}

State-based transitions

function StateMachine({ frame }) {
  const states = [
    { name: 'idle', from: 0 },
    { name: 'loading', from: 60 },
    { name: 'ready', from: 120 },
    { name: 'complete', from: 180 }
  ];
  
  const currentStateIndex = states.findIndex((s, i) => {
    const next = states[i + 1];
    return frame >= s.from && (!next || frame < next.from);
  });
  
  const currentState = states[currentStateIndex];
  const nextState = states[currentStateIndex + 1];
  
  let transitionProgress = 0;
  if (nextState) {
    const transitionStart = nextState.from - 20;
    if (frame >= transitionStart) {
      transitionProgress = transition(frame, transitionStart, 20);
    }
  }
  
  return (
    <StateRenderer
      state={currentState.name}
      nextState={nextState?.name}
      transitionProgress={transitionProgress}
    />
  );
}

Combining with spring physics

import { transition, spring, Easing } from '@helios-project/core';

function HybridTransition({ frame }) {
  // Use transition for opacity
  const opacity = transition(frame, 0, 30, {
    easing: Easing.cubic.out
  });
  
  // Use spring for scale (more natural bounce)
  const scale = spring({
    frame,
    fps: 30,
    from: 0,
    to: 1,
    config: { stiffness: 150, damping: 12 }
  });
  
  return (
    <div style={{
      opacity,
      transform: `scale(${scale})`
    }}>
      Content
    </div>
  );
}

Build docs developers (and LLMs) love