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 to start the transition
Duration of the transition in frames
Optional transition configuration
Easing function to apply. Use functions from the Easing object.
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 to start the crossfade
Duration of the crossfade in frames
Optional transition configuration
Easing function to apply to the crossfade
Returns
CrossfadeResult object with:
Opacity for the incoming element (0 to 1)
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>
);
}