Animation
Physics-based springs, tweens, transitions, and gesture detection.
spring(initialValue, options)
Creates a physics-based spring animation. Springs produce natural, fluid motion by simulating a damped harmonic oscillator. Unlike time-based animations, springs respond dynamically when interrupted — you can redirect a spring mid-flight and it will smoothly change course.
import { spring } from 'what-framework';
// Create a spring starting at 0
const position = spring(0, {
stiffness: 100, // Spring tension (default: 100)
damping: 10, // Friction (default: 10)
mass: 1, // Weight of the object (default: 1)
precision: 0.01, // Settle threshold (default: 0.01)
});
// Animate to a new value
position.set(200);
// Read current animated value (reactive)
console.log(position.current());
// Check animation state
console.log(position.isAnimating()); // true while moving
console.log(position.velocity()); // current speed
The returned object exposes these reactive accessors and methods:
current()— the current animated value (reactive signal)target()— the value the spring is heading towardvelocity()— the current velocity of the animationisAnimating()— whether the spring is still in motionset(target)— start animating toward a new targetstop()— halt the animation at its current positionsnap(value)— immediately jump to a value (no animation)
Automatic Cleanup
When created inside a component, the spring's requestAnimationFrame loop is automatically cancelled when the component unmounts. No manual cleanup needed.
Example: Animated Counter
A counter that springs smoothly between values instead of jumping:
import { signal, effect } from 'what-framework';
import { spring } from 'what-framework';
function AnimatedCounter() {
const count = signal(0);
const display = spring(0, { stiffness: 120, damping: 14 });
// When count changes, animate the display value
effect(() => display.set(count()));
return (
<div>
<span>{() => Math.round(display.current())}</span>
<button onClick={() => count.set(c => c + 10)}>+10</button>
</div>
);
}
tween(from, to, options)
Creates a time-based animation that interpolates from one value to another over a fixed duration. Unlike springs, tweens have a predictable end time and use easing functions to control their curve.
import { tween, easings } from 'what-framework';
// Animate from 0 to 100 over 500ms
const anim = tween(0, 100, {
duration: 500, // milliseconds (default: 300)
easing: easings.easeOutCubic, // easing function
onUpdate: (value, t) => {}, // called each frame
onComplete: () => {}, // called when finished
});
// Reactive accessors
anim.progress(); // 0 to 1, raw progress
anim.value(); // interpolated value (0 to 100)
anim.isAnimating(); // true while running
// Cancel mid-animation
anim.cancel();
Example: Progress Bar
Animate a progress bar from 0% to 100% with a bounce easing:
import { tween, easings } from 'what-framework';
function ProgressBar() {
const bar = tween(0, 100, {
duration: 1200,
easing: easings.easeOutBounce,
});
return (
<div class="progress-track">
<div
class="progress-fill"
style={() => `width: ${bar.value()}%`}
/>
</div>
);
}
Easing Functions
The easings object provides a set of common easing curves. Each function maps an input t (0 to 1) to an output value:
import { easings } from 'what-framework';
// Available easings:
easings.linear // constant speed
easings.easeInQuad // accelerate from zero
easings.easeOutQuad // decelerate to zero
easings.easeInOutQuad // accelerate then decelerate
easings.easeInCubic // stronger acceleration
easings.easeOutCubic // stronger deceleration
easings.easeInOutCubic // stronger ease in/out
easings.easeInElastic // elastic wind-up
easings.easeOutElastic // elastic overshoot
easings.easeOutBounce // bouncing settle
Pass any easing to tween() or useTransition():
const anim = tween(0, 100, { easing: easings.easeOutBounce });
// Or use a custom easing function
const customEase = (t) => t * t * t;
const anim2 = tween(0, 100, { easing: customEase });
useTransition
Orchestrate state transitions with a progress signal. Useful for coordinating page transitions, view swaps, or multi-step animations where you need to know when the transition completes.
import { useTransition, easings } from 'what-framework';
const transition = useTransition({
duration: 400, // ms (default: 300)
easing: easings.easeOutCubic, // easing curve
});
// Reactive state
transition.isTransitioning(); // true during animation
transition.progress(); // 0 to 1 (eased)
// Start a transition — returns a Promise
await transition.start(() => {
// Callback runs when transition completes
swapContent();
});
Example: Page Transition
import { useTransition } from 'what-framework';
import { signal } from 'what-framework';
function PageContainer() {
const currentPage = signal('home');
const fade = useTransition({ duration: 250 });
async function navigate(page) {
await fade.start(() => currentPage.set(page));
}
return (
<div style={() => `opacity: ${1 - fade.progress()}`}>
{() => renderPage(currentPage())}
</div>
);
}
useGesture
Attach multi-touch gesture handlers to an element. Supports drag, swipe, pinch, tap, and long press with velocity tracking. Works with both mouse and touch events.
import { useGesture } from 'what-framework';
const cardRef = { current: null };
const gesture = useGesture(cardRef, {
onDragStart: ({ x, y }) => {},
onDrag: ({ x, y, deltaX, deltaY, velocity }) => {},
onDragEnd: ({ deltaX, deltaY, velocity }) => {},
onSwipe: ({ direction, velocity }) => {},
onPinch: ({ scale, centerX, centerY }) => {},
onTap: ({ x, y }) => {},
onLongPress: ({ x, y }) => {},
preventDefault: false,
});
The returned state object provides reactive signals:
isDragging()— whether a drag is in progresscurrentX(),currentY()— current pointer positiondeltaX(),deltaY()— distance from drag startvelocity()— current velocity as{ x, y }in px/sec
Example: Draggable Card
import { useGesture, spring } from 'what-framework';
import { effect } from 'what-framework';
function DraggableCard() {
const ref = { current: null };
const x = spring(0);
const y = spring(0);
useGesture(ref, {
onDrag: ({ deltaX, deltaY }) => {
x.snap(deltaX);
y.snap(deltaY);
},
onDragEnd: () => {
// Spring back to origin
x.set(0);
y.set(0);
},
});
return (
<div
ref={ref}
style={() => `transform: translate(${x.current()}px, ${y.current()}px)`}
class="card"
>
Drag me!
</div>
);
}
Touch Events and Scrolling
By default, touch events use { passive: true } to avoid blocking scroll. Set preventDefault: true if you need to call e.preventDefault() in your handlers (e.g., for a draggable element that should not scroll the page).
useAnimatedValue
A React Native-inspired API that wraps a signal with animation methods. Useful when you need to drive multiple animations from a single value or interpolate between ranges.
import { useAnimatedValue } from 'what-framework';
const progress = useAnimatedValue(0);
// Spring to a target
progress.spring(100, { stiffness: 120, damping: 14 });
// Or tween with timing
progress.timing(100, { duration: 500 });
// Read the current value
progress.value(); // reactive
// Interpolate into a different range
const opacity = progress.interpolate(
[0, 50, 100], // input range
[0, 0.5, 1] // output range
);
// Use in JSX
<div style={() => `opacity: ${opacity()}`} />
CSS Transitions
For CSS-class-based animations, use createTransitionClasses() and cssTransition(). These follow the enter/exit pattern used by CSS animation libraries.
import { createTransitionClasses, cssTransition } from 'what-framework';
// Generate class names for a transition called "fade"
const classes = createTransitionClasses('fade');
// {
// enter: 'fade-enter',
// enterActive: 'fade-enter-active',
// enterDone: 'fade-enter-done',
// exit: 'fade-exit',
// exitActive: 'fade-exit-active',
// exitDone: 'fade-exit-done',
// }
// Apply the transition to an element
await cssTransition(element, 'fade', 'enter', 300);
// Adds fade-enter, then fade-enter-active, then fade-enter-done
Define matching CSS classes in your stylesheet:
/* CSS for fade transition */
.fade-enter { opacity: 0; }
.fade-enter-active { opacity: 1; transition: opacity 300ms; }
.fade-enter-done { opacity: 1; }
.fade-exit { opacity: 1; }
.fade-exit-active { opacity: 0; transition: opacity 300ms; }
.fade-exit-done { opacity: 0; }
Live Demo
Click the buttons below to see a physics-based spring animation in action. The black square animates to the target position with natural overshoot and settle.