Metamorphosis
A spring drives progress through five Frame Engine keyframes. The orb morphs color, border-radius, scale, and glow — all with natural spring overshoot and settle.
view the spring
const morphSpring = new PhysicsEngine({
attraction: 0.011,
friction: 0.11,
});
morphSpring.on("change", ({ position, progress }) => {
const styles = morphFrames.getFrame(progress);
applyStyles(morphOrb, styles);
morphOrb.style.boxShadow =
`0 0 60px ${styles.backgroundColor}55, 0 0 120px ${styles.backgroundColor}22`;
renderReadout(morphReadout, {
progress: progress.toFixed(3),
position: position.toFixed(1),
backgroundColor: styles.backgroundColor,
borderRadius: styles.borderRadius,
transform: styles.transform,
});
});
document.getElementById("morph-go").addEventListener("click", () => {
morphSpring.animateTo(0, 2000, 10);
});
Spring Traverse
Physics spring propels a shape across the stage. Frame Engine maps the spring progress to compound transforms, color shifts, and border-radius — each interpolated independently.
view the spring
const traverseSpring = new PhysicsEngine({
attraction: 0.03,
friction: 0.11,
});
traverseSpring.on("change", ({ position, progress }) => {
const frames = traverseForward
? traverseForwardFrames
: traverseReturnFrames;
const styles = frames.getFrame(progress);
applyStyles(traverseBox, styles);
traverseBox.style.boxShadow =
`0 8px 40px ${styles.backgroundColor}33`;
renderReadout(traverseReadout, {
progress: progress.toFixed(3),
position: position.toFixed(1),
transform: styles.transform,
backgroundColor: styles.backgroundColor,
borderRadius: styles.borderRadius,
});
});
traverseGoBtn.addEventListener("click", () => {
traverseGoBtn.disabled = true;
traverseSpring.animateTo(0, 1200, 20);
});
Parameter Lab
Adjust attraction and friction to feel how spring dynamics change. Higher attraction snaps faster. Higher friction damps the bounce. The shape auto-animates on each change.
view the spring
function labAnimate() {
const attraction =
parseInt(document.getElementById("lab-attraction").value) / 1000;
const friction =
parseInt(document.getElementById("lab-friction").value) / 100;
const distance = parseInt(
document.getElementById("lab-distance").value,
);
const velocity = parseInt(
document.getElementById("lab-velocity").value,
);
labSpring.stop();
labSpring = new PhysicsEngine({ attraction, friction });
wireLabSpring();
labSpring.animateTo(0, distance, velocity);
}
document.getElementById("lab-run")
.addEventListener("click", labAnimate);
Cascade
Eight elements, each driven by its own spring, launched in sequence. The same Frame Engine keyframes applied to every dot — the staggered timing creates a wave of color and motion.
view the spring
function cascadeLaunch(forward) {
cascadeSprings.forEach((spring) => spring.stop());
cascadeSprings.length = 0;
cascadeDots.forEach((dot, i) => {
setTimeout(() => {
const spring = new PhysicsEngine();
cascadeSprings.push(spring);
spring.on("change", ({ progress }) => {
const styles = cascadeFrames.getFrame(progress);
applyStyles(dot, styles);
dot.style.boxShadow =
`0 4px 24px ${styles.backgroundColor}44`;
});
if (forward) {
spring.animateTo(0, 1200, 20);
} else {
spring.animateTo(1200, 0, -20);
}
}, i * 60);
});
}