You're reading the v2 beta docs. For the stable release, switch to v1 →
TakumiTakumi

Keyframe Animation

Animate scenes with CSS keyframes and render them as GIFs, WebP, APNG, or video frames.

Takumi animates scenes two ways:

  • renderAnimation() produces an animated webp, apng, or gif directly.
  • render() with timeMs renders one frame at a time, for structured keyframes or external encoders like ffmpeg.

Below is a keyframe animation rendered with ffmpeg + shiki for syntax highlighting:

How to define animations

Structured keyframes objects

Use structured keyframes when you render a specific animation frame with render() and timeMs. render() is the only API that accepts the keyframes option; renderAnimation() does not.

A keyframes object maps each animation name to a record of selector keys. Accepted selector keys:

KeyPosition
fromstart of the animation
toend of the animation
"<n>%"a percentage offset, e.g. "50%"
render.tsx
import {  } from "takumi-js";

const  = await (< ="animate-[move_1s_ease-in-out_infinite_alternate]" />, {
  : 100,
  : 100,
  : "png",
  : 500,
  : {
    : {
      : {
        : "translateX(0)",
      },
      "50%": {
        : "translateX(60px)",
      },
      : {
        : "translateX(120px)",
      },
    },
  },
});

CSS Stylesheets

Use stylesheet @keyframes when you want the animation to travel with the JSX tree and then render it with either renderAnimation() or render() + stylesheets.

renderAnimation() takes the scenes directly: each scene's JSX (or HTML string, or node tree) is transformed and its <style> is extracted automatically, and image resources are fetched once across every scene.

render.tsx
import {  } from "takumi-js";

const  = await ({
  : 100,
  : 100,
  : 30,
  : "webp",
  : [
    {
      : 1000,
      : (
        < ="w-full h-full items-center justify-center">
          <>{`
            @keyframes move {
              from {
                transform: translateX(0);
              }

              to {
                transform: translateX(60px);
              }
            }
          `}</>
          < ="w-10 h-10 bg-red-500 animate-[move_1s_ease-in-out_infinite_alternate]" />
        </>
      ),
    },
  ],
});

Tailwind animation utilities

Takumi supports these Tailwind animation forms:

  • Presets: see the table below.
  • Arbitrary shorthand values: animate-[move_1s_ease-in-out_infinite_alternate]
PresetEffect
animate-noneno animation
animate-spincontinuous rotation
animate-pingscale and fade out, like a radar ping
animate-pulsefade opacity in and out
animate-bouncebounce up and down

For arbitrary values, _ is converted to a space, so:

animate-[move_1s_ease-in-out_infinite_alternate]

becomes:

animation: move 1s ease-in-out infinite alternate;

Takumi does not currently support Tailwind's animate-(--custom-property) form because CSS custom property resolution for animation is not implemented.

Ways to render

The next two examples share this file.

scene.tsx
import type {  } from "takumi-js";

export const :  = {
  : {
    : {
      : "translateX(0)",
    },
    : {
      : "translateX(60px)",
    },
  },
};

// `keyframes` feeds render(); the inline <style> travels with the tree for renderAnimation().
export function () {
  return (
    < ="w-full h-full items-center justify-center">
      <>{`
        @keyframes move {
          from {
            transform: translateX(0);
          }

          to {
            transform: translateX(60px);
          }
        }
      `}</>
      < ="w-10 h-10 bg-red-500 animate-[move_1s_ease-in-out_infinite_alternate]" />
    </>
  );
}

render()

Use render() when you want a single frame at a specific animation time.

render.tsx
import {  } from "takumi-js";
import { ,  } from "./scene";

const  = await (< />, {
  : 100,
  : 100,
  : "png",
  ,
  : 500,
});

renderAnimation()

renderAnimation() has no keyframes option. Animate the scene with stylesheet keyframes (an inline <style>, as in Scene above) or Tailwind's animation presets. scenes is an array so you can stitch several into one animation, but Takumi does not tween between them.

render.tsx
import {  } from "takumi-js";
import {  } from "./scene";

const  = await ({
  : 100,
  : 100,
  : 30,
  : "webp",
  : [{ : 1000, : < /> }],
});

render() + ffmpeg

The ffmpeg-keyframe-animation example renders raw frames with render() and streams them into ffmpeg. Use it for video output.

Build the scene once, render each frame at a specific timeMs, and pipe the raw RGBA frames into ffmpeg.

render.tsx
import {  } from "takumi-js";
import {  } from "bun";
import { ,  } from "./scene";

const  = 30;
const  = 4;
const  = 1200;
const  = 630;
const  =  * ;

const  = (
  [
    "ffmpeg",
    "-y",
    "-f",
    "rawvideo",
    "-pixel_format",
    "rgba",
    "-video_size",
    `${}x${}`,
    "-framerate",
    `${}`,
    "-i",
    "pipe:0",
    "output.mp4",
  ],
  { : "pipe", : "ignore", : "ignore" },
);

const  = < />;

for (let  = 0;  < ; ++) {
  const  = ( / ) * 1000;
  const  = await (, {
    ,
    ,
    : "raw",
    ,
    ,
  });

  ..();
}

..();
await .;
Edit on GitHub

Last updated on

On this page