Animate on the timeline
Keyframes, easing curves and springs live in node.animations — raw data, never rasterized.
Blinn Motion turns a Figma Motion timeline into a tinyMotionDoc and plays it with a pure-JS engine — the same motion, frame for frame, on every platform.
No black box in the middle. Your animation becomes an open document, then a deterministic render tree — the same maths everywhere it plays.
Keyframes, easing curves and springs live in node.animations — raw data, never rasterized.
The plugin reads the timeline and emits a tiny MotionDoc — no PNGs, no video, just your motion.
The pure render method walks the doc, solves every track at time t, returns a resolved tree of final numbers.
Every adapter paints the same resolved render tree, so an animation looks and times identically wherever it runs. The adapters are thin; the engine does the thinking.
Full-fidelity CSS adapter — nested divs, gradients, SVG vector paths with arrowheads, clip-path stars, masks and procedural shaders.
import { create } from "@blinn-motion/dom";
create(el, doc, { loop: true }).play();A pure-JS 2D canvas painter for the very same resolved tree — one immediate-mode draw per frame, no DOM nodes.
import { create } from "@blinn-motion/canvas";
create(el, doc, { loop: true }).play();A declarative component and hook. Switch the painter with one prop; play, pause and seek through a ref.
<BlinnMotion doc={doc} renderer="canvas" loop autoplay />The same timing on native <View> / <Text>. No native module, no Skia dependency — just the shared core maths.
<BlinnMotionView doc={doc} loop />@blinn-motion/core is time-based and DOM-free. The whole engine hangs off a single deterministic function — everything else is a thin painter.
// THE render method — pure, DOM-free
export function sample(doc: MotionDoc, t: number) return doc.layers.map((layer) =>
resolve(layer, t) // walk + sample every track
);
// each track, eased + composed over the base
const v = ease(key.easing, local);
node[p] = op === "offset"
? base + lerp(a, b, v) // additive
: lerp(a, b, v); // replace
// → resolved RenderNode tree (final numbers)The core speaks one language: t in seconds. The playback clock is shared, so play / pause / seek / loop behave the same everywhere.
Multiple tracks per property compose over the base value — set replaces, offset adds — so springs and curves layer cleanly.
Every transform, RGBA color and shape vertex comes out resolved. Adapters never re-interpret intent; they just paint.
A small core with a sharp focus: resolve motion to numbers, then paint it the same way on every surface you ship to.
The maths is DOM-free and unit-tested. Only the adapters touch a platform — the engine runs anywhere JS does.
Linear, hold, cubic-bezier (Newton-Raphson) and damped springs — sampled per frame, not pre-baked.
A small, readable JSON document you own. Inspectable, diffable, versionable — no proprietary blob.
Same resolved tree, same shared clock. An animation times the same on DOM, Canvas, React & Native.
Drive playback frame-accurately: seek(s), seekFraction(0…1), loop and variable speed — all from the core.
Nested transforms, linear & radial gradients, SVG vector paths, clip-path stars, masks and procedural shaders.
No runtime dependencies in the core. Drop it into vanilla JS, React or React Native without ceremony.
Select an animated frame, preview it live, and download the MotionDoc — straight from the Motion timeline.
Hand the engine a MotionDoc and a host element. Pick a renderer, press play. The control surface — play, pause, seek, seekFraction, loop — is identical across every adapter.
npm i @blinn-motion/domimport { create } from "@blinn-motion/dom";
import doc from "./card.motion.json";
const player = create(
document.getElementById("stage")!,
doc,
{ loop: true }
);
player.play(); // pause(), toggle()
player.seek(0.8); // seconds
player.seekFraction(0.5); // 0…1import { create } from "@blinn-motion/canvas";
import doc from "./card.motion.json";
// same call, a pure-JS 2D painter
const player = create(canvasHost, doc, {
loop: true,
});
player.play();import { BlinnMotion } from "@blinn-motion/react";
import doc from "./card.motion.json";
export default function Hero() {
return (
<BlinnMotion
doc={doc}
renderer="canvas" // or "dom"
loop
autoplay
/>
);
}import { BlinnMotionView } from "@blinn-motion/react-native";
import doc from "./card.motion.json";
export function Splash() {
return <BlinnMotionView doc={doc} loop autoplay />;
}Lottie proved the idea: ship motion as data, not video. Blinn Motion takes that idea to the Figma Motion era with its own format and its own runtime — so you own the whole pipeline.
Lottie is a trademark of its respective owners. Blinn Motion is an independent, MIT-licensed project — not affiliated.
Export from Figma Motion, drop the MotionDoc into your app, and let the engine do the rest — on the web, in React, on native.