Skip to content

Interpolators

Interpolators are functions that compute intermediate values between two endpoints. They are the engine behind Ripl's animation system — when you transition an element's radius from 50 to 100, an interpolator generates all the in-between values.

Ripl automatically selects the right interpolator based on the value type, but you can also provide custom interpolators for specialized behavior.

NOTE

For the full API, see the Core API Reference.

Built-in Interpolators

Ripl ships with interpolators for common value types. They are tested in order — the first one whose test function returns true is used. Built-in factories include interpolateNumber, interpolateColor (hex, rgb, rgba, hsl), interpolateGradient, interpolateDate, interpolatePath (SVG path strings), interpolateString (extracts numeric values), and interpolateAny (fallback — snaps at t > 0.5).

How Interpolators Work

An interpolator factory takes two values (start and end) and returns a function that accepts a time value t (0 to 1) and returns the interpolated result:

ts
import {
    interpolateNumber,
} from '@ripl/web';

const interpolate = interpolateNumber(0, 100);

interpolate(0); // 0
interpolate(0.5); // 50
interpolate(1); // 100

Number Interpolation

The simplest interpolator — linear interpolation between two numbers:

ts
const interpolate = interpolateNumber(10, 50);
interpolate(0.25); // 20
interpolate(0.75); // 40

Color Interpolation

Interpolates between CSS color strings by parsing them to RGBA, interpolating each channel, and serializing back:

ts
import {
    interpolateColor,
} from '@ripl/web';

const interpolate = interpolateColor('#3a86ff', '#ff006e');
interpolate(0); // 'rgba(58, 134, 255, 1)'
interpolate(0.5); // 'rgba(157, 67, 162, 1)' (midpoint)
interpolate(1); // 'rgba(255, 0, 110, 1)'

Color interpolation works across different color formats — you can interpolate from a hex color to an RGB color seamlessly.

Any Interpolation

The fallback interpolator for values that don't match any other type. It snaps to the target value at the halfway point:

ts
import {
    interpolateAny,
} from '@ripl/web';

const interpolate = interpolateAny('hello', 'world');
interpolate(0.3); // 'hello'
interpolate(0.7); // 'world'

Automatic Interpolation

When you use element.interpolate() or renderer.transition(), Ripl automatically selects the appropriate interpolator for each property:

ts
await renderer.transition(circle, {
    duration: 1000,
    state: {
        radius: 100, // uses interpolateNumber
        fill: '#ff006e', // uses interpolateColor
    },
});

Custom Interpolators

Inline Interpolator

The simplest way to use a custom interpolator is to pass a function directly in the transition state:

ts
await renderer.transition(circle, {
    duration: 1000,
    state: {
        // Custom function: t goes from 0 to 1
        radius: t => 50 + Math.sin(t * Math.PI) * 50,
    },
});

InterpolatorFactory

For reusable interpolators, create an InterpolatorFactory — a function that takes start and end values and returns an interpolator:

ts
import type {
    InterpolatorFactory,
} from '@ripl/web';

const interpolateBoolean: InterpolatorFactory<boolean> = (a, b) => {
    return t => t > 0.5 ? b : a;
};

// Optional: add a test function so it's auto-selected
interpolateBoolean.test = (value) => typeof value === 'boolean';

Keyframe Values

Transitions also support keyframe-style arrays for multi-step animations:

ts
await renderer.transition(circle, {
    duration: 1000,
    state: {
        // Implicit offsets (evenly spaced)
        fill: ['#3a86ff', '#ff006e', '#8338ec'],

        // Explicit offsets
        radius: [
            {
                value: 80,
                offset: 0.3,
            },
            {
                value: 40,
                offset: 0.7,
            },
            {
                value: 100,
                offset: 1.0,
            },
        ],
    },
});

The Interpolation Pipeline

When a transition runs, here's what happens for each property:

  1. Read the current value from the element
  2. Select an interpolator (custom function, keyframe array, or auto-detected factory)
  3. On each frame, compute the eased time t
  4. Apply the interpolated value to the element
  5. The renderer re-renders the scene

This pipeline runs for every animated property simultaneously, producing smooth multi-property transitions.

Demos

Each demo below lets you scrub through interpolation time t (0→1) to see the interpolator in action.

Number

Linear interpolation between two numbers — the foundation of all other interpolators.

ts
import {
    interpolateNumber,
} from '@ripl/web';

const interp = interpolateNumber(20, 120);
circle.radius = interp(t);

Color

Interpolates between CSS color strings by parsing to RGBA, interpolating each channel independently, and serializing back.

ts
import {
    interpolateColor,
} from '@ripl/web';

const interp = interpolateColor('#3a86ff', '#ff006e');
rect.fill = interp(t);

Gradient

Transitions between two CSS gradient strings by interpolating their stop colors, offsets, and angles.

ts
import {
    interpolateGradient,
} from '@ripl/web';

const interp = interpolateGradient(
    'linear-gradient(0deg, #3a86ff, #8338ec)',
    'linear-gradient(180deg, #ff006e, #fb5607)'
);
rect.fill = interp(t);

Rotation

Interpolates between rotation values — supports numbers (radians) and strings like "90deg" or "1.5rad".

ts
import {
    interpolateRotation,
} from '@ripl/web';

const interp = interpolateRotation('0deg', '360deg');
rect.rotation = interp(t);

Path

Progressively reveals a polyline path from start to end as t advances from 0 to 1.

ts
import {
    getPolygonPoints, interpolatePath,
} from '@ripl/web';

const points = getPolygonPoints(6, cx, cy, radius, true);
const interp = interpolatePath(points);
polyline.points = interp(t);

Point Interpolation & Shape Morphing

interpolatePoints transitions between two point arrays. When the arrays differ in length, the shorter set is automatically extrapolated — intermediate points are inserted along its edges so both arrays have equal length. This enables smooth morphing between any two polygon shapes.

ts
import {
    getPolygonPoints, interpolatePoints,
} from '@ripl/web';

const triangle = getPolygonPoints(3, cx, cy, radius);
const octagon = getPolygonPoints(8, cx, cy, radius);

const interp = interpolatePoints(triangle, octagon);
polygon.points = interp(t); // smoothly morphs between shapes