TakumiTakumi

Keyframe Animation

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

Takumi supports two animation workflows:

  • Use renderAnimation() when you want a simple animated webp, apng, or gif.
  • Use render() with timeMs when you want full control over frame rendering and external encoding tools like ffmpeg.

This is an example of a keyframe animation rendered with ffmpeg + shiki for syntax highlighting:

This guide uses the following scene.tsx example.

scene.tsx
const stylesheets = `
  .box {
    animation: move 1000ms ease-in-out infinite alternate;
  }

  @keyframes move {
    from {
      transform: translateX(0);
    }

    to {
      transform: translateX(60px);
    }
  }
`;

export function Scene() {
  return (
    <div tw="w-full h-full items-center justify-center">
      <style>{stylesheets}</style>
      <div className="box" tw="w-10 h-10 bg-red-500" />
    </div>
  );
}

renderAnimation()

This is the minimal API. In most cases, you should keep a single scene and animate it with CSS keyframes. The scenes field is still an array because you may want to compose multiple scenes into a single animated image, but Takumi does not generate transitions between them.

render.tsx
import {  } from "@takumi-rs/core";
import {  } from "@takumi-rs/helpers/jsx";
import {  } from "./scene";

const  = new ();

const { ,  } = await (
  < />,
);

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

render() + ffmpeg

The ffmpeg-keyframe-animation example renders raw frames with render() and streams them into ffmpeg. This is the better route when you want video output or tighter control over the pipeline.

The core idea is:

  1. Build the scene once.
  2. Render each frame at a specific timeMs.
  3. Pipe the raw RGBA frames into ffmpeg.
render.tsx
import {  } from "@takumi-rs/core";
import {  } from "@takumi-rs/helpers/jsx";
import {  } from "bun";

const  = new ();
const  = 30;
const  = 4;
const  = 1200;
const  = 630;
const  =  * ;

const { ,  } = await (<Scene />);

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

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

  ..();
}

..();
await .;
Edit on GitHub

Last updated on

On this page