Core
The spring every moving part runs on, boxes read as the browser draws them, and the attributes the packages recognize each other by.
On this page
Spring
SpringAnimator animates several numbers at once with springs set by a duration in seconds and a bounce from 0 to 1. It’s exact at any frame rate. Changing the target keeps the current velocity, so reversing mid-way stays smooth, which CSS transitions can’t do.
pnpm add @fluidkit/coreimport { SpringAnimator } from "@fluidkit/core";
const spring = new SpringAnimator(
{ width: 100, radius: 12 },
{ duration: 0.4, bounce: 0.2 },
({ width, radius }) => {
el.style.width = `${width}px`;
el.style.borderRadius = `${radius}px`;
},
() => console.log("at rest"), // optional
);
spring.to({ width: 240, radius: 24 });
spring.to({ width: 100, radius: 12 }, { duration: 0.6, bounce: 0 }); // new springs are optional
spring.retarget({ width: 110, radius: 12 }); // adjust the current movement, no new bounce
spring.jump({ width: 120 }); // move a channel at once, keeping its velocity and target
spring.values(); // where it is now
spring.velocities(); // how fast each channel moves, units per second: hand it on with to()'s third argument
spring.stop();Each channel can have a spring of its own:
new SpringAnimator(
{ opacity: 0, scale: 1 },
{
opacity: { duration: 0.15, bounce: 0 },
scale: { duration: 0.45, bounce: 0.3 },
},
({ opacity, scale }) => {
/* … */
},
);SpringParams is { duration, bounce, settle? }. duration is how long the motion feels, in seconds; it isn’t a hard deadline. bounce runs from 0 (no overshoot) to 1; below 1 the spring comes to rest. settle is the bounce after the first overshoot, bounce by default: a bouncy spring with a low settle overshoots once and then comes back without swinging. to() starts a new movement, with its bounce; retarget() changes the target of the current one without a new bounce.
Boxes
What morph and indicator read an element by: where it is, its size, and its corners as the browser draws them, so something else can take its shape. Corners are circular, never an oval: a percent resolves against the box, and an elliptical corner takes the smaller of its two radii. Where two corners on a side ask for more than the side has, all of them shrink by the same factor, so a capsule’s 9999px comes out as half its height.
import { measureBox, radiiCss, still } from "@fluidkit/core";
// Its box on screen, without the inline transforms a press left on it
const box = still(el, () => measureBox(el, true));
// { x, y, width, height, radii: [tl, tr, br, bl], shape }
other.style.borderRadius = radiiCss(box.radii);| Export | Meaning |
|---|---|
measureBox(el, cornerShapes) |
Its box on screen, its corners’ radii, and the top-left corner’s shape as a superellipse exponent (round 1, squircle 2); 1 unless cornerShapes |
radiiOf(style, width, height) |
The four corners from a computed style, in a box of that size, fitted |
cornerOf(value, width, height) |
One computed corner (10px, 10%, 10px 20px) as one circular radius |
fitRadii(radii, width, height) |
Radii shrunk as the browser shrinks them where they don’t fit |
cornerShapeOf(style) |
The top-left corner’s corner-shape as a superellipse exponent, so it interpolates |
drawsCornerShape(window) |
Whether that browser draws corner-shape |
radiiCss(radii, scale?) |
Radii as CSS, one value when they’re all the same |
still(el, read, to?) |
Reads with the element’s inline transforms off, put back in the same task, so nothing in between is painted |
px(value) |
A length to a hundredth of a pixel |
Contract
The packages never import each other. Instead, each marks what it does with attributes in the DOM and reads the attributes the others set, so each works alone and any two work together. The names are exported as constants and change only in a major version of every package.
| Attribute | Constant | Set by | Meaning |
|---|---|---|---|
data-fk-surface |
SURFACE |
Liquid Blur’s melt, on a group’s root while it runs | Its children share one surface: a morph from one of them lifts its glass over the others or melts with them |
data-fk-part |
PART |
Melt and morph, on elements they create; indicator, on its indicator | Not content: a morph doesn’t count it as a neighbor, press doesn’t press it |
data-fk-away |
AWAY |
Morph, on states that stepped out for its shape | Not glass for now: melt leaves it out. One that kept focus is still focusable |