Skip to content

Core Concepts

Elucim animations are frame-based, not time-based. Every animation is defined in terms of frames:

frame 0frame 30frame 60frame 90circle fades intext appearsboth visible

Convert between frames and seconds:

  • frames = seconds × fps → 2 seconds at 30fps = 60 frames
  • seconds = frames / fps → 90 frames at 30fps = 3 seconds

Elucim uses SVG coordinates:

  • Origin (0, 0) is the top-left corner
  • X increases rightward
  • Y increases downward
(0, 0)(width, 0)(0, height)(width, height)XY(cx, cy) = center

For math primitives like <Axes>, you specify an origin point that becomes the mathematical (0,0), and a scale factor that maps math units to pixels.

Elucim has three layers of composition:

The visual elements: Circle, Line, Arrow, Rect, Text, Polygon, Axes, FunctionPlot, Vector, VectorField, Matrix, Graph, LaTeX, BarChart.

All primitives accept spatial transforms (rotation, scale, translate) and stacking (zIndex) as props. See Transforms & Composition for details.

Wrappers that animate their children: FadeIn, FadeOut, Draw, Write, Transform, Morph, Stagger, Parallel.

  • <Sequence from={N} durationInFrames={D}> — Offsets children to start at frame N
  • <Player> — Root container that drives the global frame counter
  • Multiple Sequence blocks run concurrently — they don’t block each other
<Player width={800} height={600} fps={30} durationInFrames={120}>
{/* Layer 1: Background axes (immediate) */}
<Axes origin={[400, 300]} xRange={[-5, 5]} yRange={[-3, 3]} scale={50} />
{/* Layer 2: Function appears at frame 20 */}
<Sequence from={20} durationInFrames={100}>
<Draw duration={40}>
<FunctionPlot fn={(x) => Math.sin(x)}
domain={[-5, 5]} origin={[400, 300]} scale={50}
color="#6c5ce7" />
</Draw>
</Sequence>
{/* Layer 3: Label at frame 50 */}
<Sequence from={50} durationInFrames={70}>
<FadeIn duration={15}>
<LaTeX expression="f(x) = \sin(x)" x={600} y={100} fontSize={20} />
</FadeIn>
</Sequence>
</Player>

Two hooks let you create custom animated components:

  • useCurrentFrame() — Returns the current frame number (0 to durationInFrames-1)
  • interpolate(frame, inputRange, outputRange, options?) — Maps a frame to a value with easing
function PulsingDot() {
const frame = useCurrentFrame();
const scale = interpolate(frame, [0, 30, 60], [1, 1.5, 1]);
const opacity = interpolate(frame, [0, 15], [0, 1]);
return (
<circle cx={250} cy={250} r={20 * scale}
fill="#6c5ce7" opacity={opacity} />
);
}