Open source · 0BSD

Motion FX Lab

Every tile below is drawn live in your browser by the same code we use to render videos to MP4, frame by frame. Pink cards are effects we have shipped in our own promo videos (not part of this repo); yellow ones are worth adding; red ones should not go on dense CJK text. Each card says how it is drawn, how hard it is and what it is for. The filter covers the 5 ways a tile is drawn; a finished video with sound uses 15 stacks, listed in the table further down.

Mascot

How good can a code-drawn character get?

The first one looks like building blocks: spheres and cylinders stacked as they are, with no outline and even proportions. The other two redraw the same character with no AI and no new dependency, and every pose is still a function of t.

What code can reach

  • A clean app-mascot or vinyl-toy look, as in options A and B.
  • Expressions, waves and hops are all driven by t, so they render frame-exact; a new outfit or expression is a few lines.
  • A's weak spot is the hair, which still reads a little like a cap.

One level up needs a person (still no AI)

  • An illustrator designs the character and hands over layered SVG; the parts are rigged the way option B is.
  • A 3D artist models it in Blender and hands over a GLB; three.js r128 ships GLTFLoader, so it loads and lights directly.
  • Out of reach for code: an illustrator's line and personality, subtle expressions, real hair and cloth motion.

Our pick: option B

  • Same flat style as the card, chat and sticker tiles in this gallery.
  • No 3D shading, so text on top stays readable.
  • The same SVG works as a post image, a chat sticker or a site avatar.
  • Option A suits the 3D scenes, next to the 3D “6%” or the chrome logo.
Status
Drawn with

One more idea, a 1–3 second seamless loop teaser, is an editing format rather than an effect, so it has no tile. The grading tile previews the look with a CSS filter; a real grade would use the LUT pass that ships with three.js r128, and the glow stays off the text. The 3D and shader tiles are scaled-down versions of the code in our videos, drawn in turn on one shared WebGL canvas.

Stack

The 15 stacks, and what each one does

15 stacks: 5 for the picture, 6 for 3D, 2 for sound and 2 for rendering. The 5 drawing methods in the filter above are the part that draws effects; the other 10 handle fonts, the timeline, 3D details, sound and output. None of the 12 “can add” effects needs a new stack.

StackWhat it isWhyWhat it does here
Rendering

From HTML to MP4, measured

Measured on a 56-second, 1,693-frame promo of ours with heavy 3D (the promo itself is not in this repo). Timed from launching the browsers to a finished lossless master. The old pipeline took 399.6 s. The recorder now renders the whole video twice and compares every frame, in 52.9 s: 7.5× faster, with nothing left unchecked. Before that, with only an 8-frame spot check, it took 26.4 s. Encoding both deliverables with every check takes another 33.6 s. All measured on 2026-09-28 on an Apple M4 laptop.

Past 4 browsers there is no gain: the bottleneck moves to the single ffmpeg process that decodes the PNGs and writes the master.

StepBeforeNowHow it is proven
3D renderingSwiftShader (GPU emulated on the CPU)ANGLE Metal (the Apple M4 GPU)The browser's WebGL renderer string is read at start; SwiftShader stops the run
Screenshotspage.screenshotCDP captureScreenshot with optimizeForSpeedAt 8 fast-moving moments the decoded pixels matched the old method 8/8, with no stale frame
Parallelism1 browser4 independent browsers, frames dealt round-robinThe whole video is rendered a second time with every frame on another browser, and all frames are compared; GPU noise stays at a few dozen pixels, 14 levels at most
Masterx264 CRF 17 (lossy)x264 losslessDeliverables are compressed once; the master must hold every frame
LookMean SSIM 0.989 between old and new; the 71 frames under 0.95 are dense particle and dust frames that look identical side by sideffmpeg ssim, frame by frame
Sound−21.2 LUFS · −2.1 dBTP · AAC 160 kbps−14.1 LUFS · −1.6 dBTP · AAC 127 kbpsLimit, then two-pass loudnorm that must report “linear”; the file is measured with ebur128. 160 kbps was over Meta's 128 kbps ceiling
High-quality filenone24.9 MB · 3.5 Mbps mean · 17.2 Mbps peak secondMeta Reels allows 25 Mbps and 300 MB; moov first, no edit list
Web preview13.8 MB13.9 MB · two-pass 1870 kbpsOver 15 MB, it retries once at a lower rate, then stops

The new checks stopped 4 runs on day one; no broken video shipped

  • A writer-loop bug: too few frames, stop.
  • Re-rendered frames differed: GPU edge noise on rotated text. The rule moved from byte-identical to a pixel tolerance; a wrong frame differs by 100,000+ pixels and still stops the run. Later, a spot check of 8 frames missed a browser that drew a label wrongly, so every frame is now rendered twice.
  • A decoder deadlock: the frame comparison, the frame counts and every delivery step now have a time limit, so a hang there fails loudly.
  • A bad argument: refused before starting.

Not done, and why

  • HEVC: the high-quality file has no size pressure and H.264 is enough; the web preview must play in every browser.
  • Less grain: the measured promo has no film grain, so it did not apply.
  • The GPU speed-up is verified on macOS only; without a Metal GPU, render with --cpu.
Not added

Stacks we looked at and left out

Later

  • three.js r128 → r186: no effect here needs it; the upgrade takes about six steps and every colour, light and custom shader must be re-tuned.
  • GSAP: add it for arbitrary shape morphs (a shield into an umbrella); free for commercial use under Webflow's own licence.
  • HyperFrames: the same approach as ours, but six months old at v0.8 with an experimental API.

No

  • Remotion: a rewrite in React, and paid licences for companies over three people.
  • lygia: commercial use needs a Patron licence.
  • Generative AI: no AI actors, footage or depth maps. A mascot redesign goes to a human illustrator; music stays synthesised in code.