Easing
The Easing element provides animation easing functions for smooth transitions and motion effects.
Access
const draw = (K: KlintContext) => {
// Access the Easing element
const t = K.time * 0.5; // 0 to 1 over 2 seconds
const eased = K.Easing.inout(t); // Apply easing
}
Basic Functions
in(val, power?)
in(val: number, power?: number) => number
Ease-in (slow start, fast end). Default power is 2.
const t = (K.time % 2) / 2; // 0 to 1 over 2 seconds
const eased = K.Easing.in(t, 3); // Cubic ease-in
K.fillColor("red");
K.circle(100 + eased * 300, 200, 20);
out(val, power?)
out(val: number, power?: number) => number
Ease-out (fast start, slow end). Default power is 2.
const t = (K.time % 2) / 2;
const eased = K.Easing.out(t, 2); // Quadratic ease-out
K.fillColor("blue");
K.circle(100, 200 + eased * 200, 20);
inout(val, power?)
inout(val: number, power?: number) => number
Ease-in-out (slow start and end, fast middle). Default power is 2.
const t = (K.time % 3) / 3;
const eased = K.Easing.inout(t, 4); // Quartic ease-in-out
K.fillColor("green");
K.circle(100 + eased * 400, 100, 25);
Utility Functions
normalize(val)
normalize(val: number) => number
Converts range [-1, 1] to [0, 1].
const oscillation = Math.sin(K.time * 2); // -1 to 1
const normalized = K.Easing.normalize(oscillation); // 0 to 1
K.opacity(normalized);
expand(val)
expand(val: number) => number
Converts range [0, 1] to [-1, 1].
const progress = (K.time % 4) / 4; // 0 to 1
const expanded = K.Easing.expand(progress); // -1 to 1
const rotation = expanded * Math.PI; // -π to π
Overshoot Easing
overshootIn(val)
overshootIn(val: number) => number
Backs up before moving forward.
const t = (K.time % 2) / 2;
const eased = K.Easing.overshootIn(t);
K.fillColor("purple");
K.circle(200 + eased * 200, 300, 15);
overshootOut(val)
overshootOut(val: number) => number
Overshoots the target then settles back.
const t = (K.time % 2) / 2;
const eased = K.Easing.overshootOut(t);
K.fillColor("orange");
K.circle(200, 300 + eased * 200, 15);
overshootInOut(val)
overshootInOut(val: number) => number
Combines overshoot in and out.
Bounce Easing
bounceIn(val)
bounceIn(val: number) => number
Bouncing motion that starts.
bounceOut(val)
bounceOut(val: number) => number
Bouncing motion that settles.
bounceInOut(val)
bounceInOut(val: number) => number
Bounce at both start and end.
const t = (K.time % 3) / 3;
const bounced = K.Easing.bounceOut(t);
K.fillColor("cyan");
K.circle(100, 100 + bounced * 300, 20);
Elastic Easing
elasticIn(val)
elasticIn(val: number) => number
Elastic motion at the start.
elasticOut(val)
elasticOut(val: number) => number
Elastic motion at the end.
elasticInOut(val)
elasticInOut(val: number) => number
Elastic motion at both ends.
const t = (K.time % 4) / 4;
const elastic = K.Easing.elasticOut(t);
K.strokeColor("yellow");
K.strokeWidth(3);
K.line(200, 400, 200 + elastic * 200, 400);
Smoothstep
smoothstep(val, x0?, x1?)
smoothstep(val: number, x0?: number, x1?: number) => number
Smooth Hermite interpolation. Default range is [0, 1].
// Custom range smoothstep
const t = K.time % 5;
const smooth = K.Easing.smoothstep(t, 1, 4); // Smooth between t=1 and t=4
K.fillColor("white");
K.opacity(smooth);
K.rectangle(50, 50, 100, 100);
Animation Examples
Staggered Animation
const draw = (K: KlintContext) => {
K.background("#222");
const numBoxes = 10;
const duration = 2; // seconds
for (let i = 0; i < numBoxes; i++) {
// Stagger the timing
const delay = i * 0.1;
const t = Math.max(0, (K.time - delay) % (duration + 1)) / duration;
const clampedT = Math.min(1, t);
// Different easing for each box
const easings = [
K.Easing.in(clampedT),
K.Easing.out(clampedT),
K.Easing.inout(clampedT),
K.Easing.bounceOut(clampedT),
K.Easing.elasticOut(clampedT)
];
const eased = easings[i % easings.length];
K.fillColor(`hsl(${i * 36}, 70%, 60%)`);
K.rectangle(50 + i * 80, 200 + eased * 200, 60, 40);
}
}
UI Transitions
const draw = (K: KlintContext) => {
const { mouse } = KlintMouse();
// Initialize UI state
if (!K.State.has('buttonHover')) {
K.State.set('buttonHover', 0);
}
const buttonX = 200;
const buttonY = 300;
const buttonW = 120;
const buttonH = 50;
// Check if mouse is over button
const isHover = mouse.x > buttonX && mouse.x < buttonX + buttonW &&
mouse.y > buttonY && mouse.y < buttonY + buttonH;
// Animate hover state
let hoverAmount = K.State.get('buttonHover');
const speed = 0.1;
hoverAmount += (isHover ? 1 : 0 - hoverAmount) * speed;
K.State.set('buttonHover', hoverAmount);
// Apply easing to hover animation
const easedHover = K.Easing.overshootOut(hoverAmount);
K.background("#333");
// Draw button with eased hover effect
K.fillColor(`rgba(100, 150, 255, ${0.3 + easedHover * 0.7})`);
K.strokeColor("white");
K.strokeWidth(2 + easedHover * 2);
K.push();
K.translate(buttonX + buttonW/2, buttonY + buttonH/2);
K.scale(1 + easedHover * 0.1);
K.roundedRectangle(-buttonW/2, -buttonH/2, buttonW, 10, buttonH);
K.pop();
// Button text
K.fillColor("white");
K.textAlign("center", "middle");
K.textSize(16);
K.text("Click Me", buttonX + buttonW/2, buttonY + buttonH/2);
}
Complex Motion
const draw = (K: KlintContext) => {
K.background("#111");
const centerX = K.width / 2;
const centerY = K.height / 2;
const numParticles = 12;
for (let i = 0; i < numParticles; i++) {
// Multiple overlapping time cycles
const t1 = (K.time * 0.3 + i * 0.5) % 1;
const t2 = (K.time * 0.7 + i * 0.3) % 1;
const t3 = (K.time * 1.1 + i * 0.1) % 1;
// Apply different easing functions
const r = 50 + K.Easing.elasticOut(t1) * 100;
const angle = t2 * Math.PI * 2;
const scale = 0.5 + K.Easing.bounceOut(t3) * 1.5;
const x = centerX + Math.cos(angle) * r;
const y = centerY + Math.sin(angle) * r;
K.fillColor(`hsl(${i * 30 + K.time * 50}, 80%, 70%)`);
K.circle(x, y, 10 * scale);
}
}
Spring
spring(val, tension?, friction?)
spring(val: number, tension?: number, friction?: number) => number
Damped spring easing. More physically expressive than elastic — feels "alive."
tension(0-1, default 0.5): Higher = faster oscillation.friction(0-1, default 0.5): Higher = more bouncy (less damping).
const t = (K.time % 2) / 2;
// Bouncy spring
const bouncy = K.Easing.spring(t, 0.5, 0.8);
// Critically damped (no overshoot)
const smooth = K.Easing.spring(t, 0.5, 0);
Steps
steps(val, n?)
steps(val: number, n?: number) => number
Staircase easing — quantizes to N discrete steps. Great for pixel art aesthetics or snapping animations.
const t = (K.time % 3) / 3;
const stepped = K.Easing.steps(t, 8); // 8 distinct levels
K.fillColor('#fff');
K.rectangle(50, 50 + stepped * 300, 40, 40);
Damp
damp(current, target, smoothing, deltaTime)
damp(current: number, target: number, smoothing: number, deltaTime: number) => number
Frame-rate independent exponential smoothing. Use instead of lerp(a, b, 0.1) which breaks at different FPS.
// Smooth camera follow (in your draw loop)
cameraX = K.Easing.damp(cameraX, player.x, 5, K.deltaTime / 1000);
cameraY = K.Easing.damp(cameraY, player.y, 5, K.deltaTime / 1000);
// Smooth mouse trailing
trailX = K.Easing.damp(trailX, mouse.x, 8, K.deltaTime / 1000);
trailY = K.Easing.damp(trailY, mouse.y, 8, K.deltaTime / 1000);
Impulse
impulse(val, k?)
impulse(val: number, k?: number) => number
Quick rise then decay. Perfect for hit effects, flashes, particles. Peak is at val = 1/k.
const t = (K.time % 1);
const flash = K.Easing.impulse(t, 6);
K.fillColor(`rgba(255, 255, 255, ${flash})`);
K.circle(K.width / 2, K.height / 2, 50 + flash * 30);
Parabola
parabola(val, k?)
parabola(val: number, k?: number) => number
Symmetric arc — 0 at edges, 1 at center. Useful for jump arcs, throw curves.
const t = (K.time % 2) / 2;
const arc = K.Easing.parabola(t);
// Jump arc
const jumpHeight = 200;
const playerY = groundY - arc * jumpHeight;
K.circle(playerX, playerY, 15);
Notes
- All easing functions expect input values between 0 and 1
- Output typically ranges from 0 to 1, but some functions (like overshoot, spring) may go beyond this range
dampis the exception — it takes current/target values and returns the smoothed result- Combine multiple easing functions for complex animations
- Use
normalize()andexpand()to convert between different ranges - For UI animations, overshoot easing feels responsive and modern
- Bounce and elastic easings work well for playful, attention-grabbing effects
- Smoothstep is excellent for crossfades and smooth transitions
- Chain easing functions:
K.Easing.inout(K.Easing.bounceOut(t)) dampis frame-rate independent — prefer it over naivelerp(a, b, 0.1)