The animator's toolbox providing comprehensive animation capabilities including keyframe, spring, and decay animations for numbers, colors, and complex strings
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Mathematical and interpolation utilities for animation calculations, including value mixing, clamping, geometric operations, and type conversion. These functions provide the building blocks for complex animations and interactions.
Core interpolation utilities for mapping values between ranges and creating smooth transitions.
/**
* Creates a function that maps input range to output range
* @param input - Array of input values (must be ascending)
* @param output - Array of output values (same length as input)
* @param options - Interpolation configuration
* @returns Function that interpolates between the ranges
*/
function interpolate<T>(
input: number[],
output: T[],
options?: InterpolateOptions<T>
): (v: number) => T;
interface InterpolateOptions<T> {
/** Whether to clamp output to range bounds (default: true) */
clamp?: boolean;
/** Easing function(s) to apply to each segment */
ease?: Easing | Easing[];
/** Custom mixer function for complex types */
mixer?: MixerFactory<T>;
}
type MixerFactory<T> = (from: T, to: T) => (v: number) => T;
/**
* Interpolates between two numbers
* @param from - Starting value
* @param to - Ending value
* @param progress - Interpolation progress (0-1)
* @returns Interpolated value
*/
function mix(from: number, to: number, progress: number): number;Usage Examples:
import { interpolate, mix } from "popmotion";
// Basic number interpolation
const mixNumbers = interpolate([0, 100], [0, 1]);
console.log(mixNumbers(50)); // 0.5
// Color interpolation
const mixColors = interpolate(
[0, 50, 100],
["#ff0000", "#00ff00", "#0000ff"]
);
console.log(mixColors(25)); // Blended red-green color
// Complex string interpolation
const mixTransforms = interpolate(
[0, 100],
["translateX(0px) scale(1)", "translateX(100px) scale(1.5)"]
);
console.log(mixTransforms(50)); // "translateX(50px) scale(1.25)"
// With custom easing per segment
const easedInterpolate = interpolate(
[0, 50, 100],
[0, 80, 100],
{ ease: ["easeOut", "easeIn"] }
);
// Simple number mixing
const currentValue = mix(0, 100, 0.75); // 75Specialized functions for interpolating between colors in different color spaces.
/**
* Creates a function to interpolate between two colors
* @param from - Starting color (hex, rgb, hsl, or color object)
* @param to - Ending color (same format as from)
* @returns Function that interpolates between colors
*/
function mixColor(from: Color | string, to: Color | string): (v: number) => string;
/**
* Interpolates between complex strings containing numbers
* @param from - Starting string with numerical values
* @param to - Ending string with same structure
* @returns Function that interpolates the numerical parts
*/
function mixComplex(from: string, to: string): (v: number) => string;
type Color = {
red: number;
green: number;
blue: number;
alpha?: number;
} | {
hue: number;
saturation: number;
lightness: number;
alpha?: number;
};Usage Examples:
import { mixColor, mixComplex } from "popmotion";
// Color mixing
const colorMixer = mixColor("#ff0000", "#0000ff");
element.style.backgroundColor = colorMixer(0.5); // Purple blend
// RGB to HSL mixing
const rgbToHsl = mixColor("rgb(255, 0, 0)", "hsl(240, 100%, 50%)");
// Complex string mixing (CSS transforms, etc.)
const transformMixer = mixComplex(
"translateX(0px) rotate(0deg) scale(1)",
"translateX(100px) rotate(180deg) scale(1.5)"
);
element.style.transform = transformMixer(0.5);
// Shadow mixing
const shadowMixer = mixComplex(
"0px 0px 0px rgba(0,0,0,0)",
"10px 10px 20px rgba(0,0,0,0.5)"
);
element.style.boxShadow = shadowMixer(progress);Core mathematical functions for value manipulation and calculations.
/**
* Constrains a value between minimum and maximum bounds
* @param min - Minimum value
* @param max - Maximum value
* @param v - Value to constrain
* @returns Clamped value
*/
function clamp(min: number, max: number, v: number): number;
/**
* Wraps a value around a range (circular behavior)
* @param min - Minimum value of range
* @param max - Maximum value of range
* @param v - Value to wrap
* @returns Wrapped value
*/
function wrap(min: number, max: number, v: number): number;
/**
* Calculates progress of a value within a range
* @param from - Start of range
* @param to - End of range
* @param value - Current value
* @returns Progress as 0-1 (can exceed bounds)
*/
function progress(from: number, to: number, value: number): number;
/**
* Creates a function that snaps values to specific points
* @param points - Snap points (number for interval, array for specific points)
* @returns Function that snaps input to nearest point
*/
function snap(points: number | number[]): (v: number) => number;Usage Examples:
import { clamp, wrap, progress, snap } from "popmotion";
// Clamp values to valid range
const opacity = clamp(0, 1, userInput); // Ensures 0-1 range
const angle = clamp(-180, 180, rotation);
// Wrap for circular behavior
const wrappedAngle = wrap(0, 360, 450); // Returns 90
const scrollPosition = wrap(0, maxScroll, currentScroll);
// Calculate progress
const scrollProgress = progress(0, 1000, scrollY); // 0.5 at 500px
const animationProgress = progress(startTime, endTime, currentTime);
// Snap to grid or specific values
const gridSnap = snap(25); // Snaps to multiples of 25
const specificSnap = snap([0, 50, 100, 150]); // Snaps to specific values
console.log(gridSnap(37)); // 25
console.log(gridSnap(63)); // 50
console.log(specificSnap(75)); // 50Functions for geometric calculations including angles, distances, and coordinate transformations.
/**
* Calculates angle between two points in degrees
* @param a - First point or origin if b not provided
* @param b - Second point (optional)
* @returns Angle in degrees
*/
function angle(a: Point, b?: Point): number;
/**
* Calculates distance between two points or values
* @param a - First point or number
* @param b - Second point or number
* @returns Distance between points
*/
function distance<P extends Point | number>(a: P, b: P): number;
/**
* Converts angle and distance to x,y coordinates
* @param angle - Angle in degrees
* @param distance - Distance from origin
* @returns Point with x,y coordinates
*/
function pointFromVector(angle: number, distance: number): Point2D;
type Point2D = {
x: number;
y: number;
};
type Point3D = Point2D & {
z: number;
};
type Point = Point2D | Point3D;Usage Examples:
import { angle, distance, pointFromVector } from "popmotion";
// Calculate angle between mouse and element
const mouseAngle = angle(
{ x: mouseX, y: mouseY },
{ x: elementX, y: elementY }
);
// Distance calculations
const dist2D = distance({ x: 0, y: 0 }, { x: 3, y: 4 }); // 5
const dist1D = distance(10, 50); // 40
const dist3D = distance(
{ x: 0, y: 0, z: 0 },
{ x: 1, y: 1, z: 1 }
); // ~1.73
// Convert polar to cartesian coordinates
const point = pointFromVector(45, 100); // { x: 70.7, y: 70.7 }
// Rotate point around origin
const rotatedPoint = pointFromVector(
angle({ x: pointX, y: pointY }) + rotationDelta,
distance({ x: 0, y: 0 }, { x: pointX, y: pointY })
);Utilities for physics calculations and animation smoothing.
/**
* Converts per-frame velocity to per-second velocity
* @param velocity - Velocity per frame
* @param frameDuration - Duration of one frame in milliseconds
* @returns Velocity per second
*/
function velocityPerSecond(velocity: number, frameDuration: number): number;
/**
* Creates function to convert per-second velocity to per-frame
* @param fps - Frames per second
* @returns Function that converts velocity
*/
function velocityPerFrame(fps: number): (velocity: number) => number;
/**
* Creates smoothing function for reducing jitter
* @param strength - Smoothing strength (0-1, higher = more smoothing)
* @returns Function that smooths input values
*/
function smooth(strength?: number): (v: number) => number;
/**
* Single-frame smoothing calculation
* @param previousValue - Previous smoothed value
* @param targetValue - New target value
* @param delta - Time delta in milliseconds
* @param strength - Smoothing strength
* @returns New smoothed value
*/
function smoothFrame(
previousValue: number,
targetValue: number,
delta: number,
strength: number
): number;Usage Examples:
import { velocityPerSecond, velocityPerFrame, smooth, smoothFrame } from "popmotion";
// Velocity conversions
const frameVel = 5; // pixels per frame
const secVel = velocityPerSecond(frameVel, 16.67); // ~300 pixels/second
const perFrameConverter = velocityPerFrame(60);
const frameVelocity = perFrameConverter(300); // 5 pixels/frame
// Smooth jittery input (like mouse movement)
const smoother = smooth(0.1);
let smoothedX = 0;
document.addEventListener('mousemove', (e) => {
smoothedX = smoother(e.clientX);
element.style.left = smoothedX + 'px';
});
// Manual smoothing with time delta
let lastValue = 0;
let lastTime = Date.now();
function updateSmoothed(newValue) {
const now = Date.now();
const delta = now - lastTime;
lastValue = smoothFrame(lastValue, newValue, delta, 0.1);
lastTime = now;
return lastValue;
}Type guard functions for runtime type checking of geometric objects.
/**
* Type guard to check if object is a Point
* @param point - Object to check
* @returns True if object has x and y properties
*/
function isPoint(point: unknown): point is Point;
/**
* Type guard to check if Point is 3D
* @param point - Point to check
* @returns True if point has z property
*/
function isPoint3D(point: Point): point is Point3D;Usage Examples:
import { isPoint, isPoint3D } from "popmotion";
function handleInput(input: unknown) {
if (isPoint(input)) {
console.log(`2D point: ${input.x}, ${input.y}`);
if (isPoint3D(input)) {
console.log(`3D point: ${input.x}, ${input.y}, ${input.z}`);
}
}
}
// Safe point operations
function safeDistance(a: unknown, b: unknown) {
if (isPoint(a) && isPoint(b)) {
return distance(a, b);
}
return 0;
}Functions for converting between different units and formats.
/**
* Converts degrees to radians
* @param degrees - Angle in degrees
* @returns Angle in radians
*/
function degreesToRadians(degrees: number): number;
/**
* Converts radians to degrees
* @param radians - Angle in radians
* @returns Angle in degrees
*/
function radiansToDegrees(radians: number): number;
/**
* Converts percentage string to decimal
* @param num - Percentage as number (e.g., 50 for "50%")
* @returns Decimal value (e.g., 0.5)
*/
function toDecimal(num: number): number;
/**
* Creates function that applies offset transformation
* @param from - Starting offset value
* @param to - Ending offset value (optional)
* @returns Function that applies offset
*/
function applyOffset(from: number, to?: number): (v: number) => number;Usage Examples:
import { degreesToRadians, radiansToDegrees, toDecimal, applyOffset } from "popmotion";
// Angle conversions
const radians = degreesToRadians(90); // π/2
const degrees = radiansToDegrees(Math.PI); // 180
// Percentage conversion
const decimal = toDecimal(75); // 0.75
// Offset transformations
const offsetter = applyOffset(10, 110); // Maps 0-1 to 10-110
const mappedValue = offsetter(0.5); // 60Specialized utilities for creating attraction effects that pull values towards target points with customizable curves.
/**
* Creates a customizable attractor function with displacement modification
* @param alterDisplacement - Function to modify displacement calculation (default: identity)
* @returns Attractor function that applies the modified displacement
*/
function createAttractor(alterDisplacement?: (displacement: number) => number):
(constant: number, origin: number, v: number) => number;
/**
* Linear attractor function that pulls values towards an origin point
* @param constant - Attraction strength (0-1, where 0 = no attraction, 1 = full)
* @param origin - Target point to attract towards
* @param v - Current value being attracted
* @returns New attracted value
*/
function attract(constant: number, origin: number, v: number): number;
/**
* Exponential attractor function with softer attraction curve
* @param constant - Attraction strength (0-1)
* @param origin - Target point to attract towards
* @param v - Current value being attracted
* @returns New attracted value with exponential curve
*/
function attractExpo(constant: number, origin: number, v: number): number;Usage Examples:
import { attract, attractExpo, createAttractor } from "popmotion";
// Linear attraction - pulls value towards origin
const result1 = attract(0.5, 10, 5); // Returns 7.5 (halfway to origin)
const result2 = attract(1.0, 10, 5); // Returns 10 (full attraction)
const result3 = attract(0.0, 10, 5); // Returns 5 (no attraction)
// Exponential attraction - softer curve
const softer1 = attractExpo(0.5, 10, 5); // Returns ~8.9 (softer than linear)
const softer2 = attractExpo(0.5, 10, 15); // Returns ~11.1
// Custom attractor with square root displacement
const customAttractor = createAttractor(Math.sqrt);
const custom = customAttractor(0.5, 10, 5); // Returns ~8.9
// Use in animations for magnetic effects
animate({
from: currentValue,
to: targetValue,
onUpdate: (value) => {
const attracted = attract(0.1, magnetPoint, value);
element.style.left = attracted + 'px';
}
});Higher-order functions for composing and chaining operations.
/**
* Composes functions to run left to right
* @param transformers - Functions to compose
* @returns Single function that applies all transformers in sequence
*/
function pipe(...transformers: Array<(v: any) => any>): (v: any) => any;Usage Examples:
import { pipe, clamp, snap, mix } from "popmotion";
// Compose multiple transformations
const processValue = pipe(
(v: number) => clamp(0, 100, v), // First clamp to 0-100
snap(10), // Then snap to multiples of 10
(v: number) => mix(0, 1, v / 100) // Finally convert to 0-1 range
);
const result = processValue(87.3); // Clamps to 87.3, snaps to 90, converts to 0.9
// Create reusable transformation chains
const normalizeAndSmooth = pipe(
(v: number) => clamp(-1, 1, v),
(v: number) => (v + 1) / 2, // Convert -1,1 to 0,1
smooth(0.1)
);Install with Tessl CLI
npx tessl i tessl/npm-popmotion