superimg

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

SuperImg Skill

SuperImg 使用指南

How It Works

工作原理

SuperImg generates videos from HTML/CSS templates. A template is a function that receives a
RenderContext
and returns an HTML string. The renderer calls this function once per frame, advancing timing values each time.
  • sceneProgress
    (0 to 1) drives animation within a scene
  • sceneTimeSeconds
    gives elapsed time for phase-based timing
  • ctx.std
    provides tween, math, and color utilities
  • ctx.data
    carries template data (merged with defaults)
Templates render to MP4 via the CLI (
superimg render
) or the server API (
renderVideo
,
loadTemplate
).
SuperImg 基于HTML/CSS模板生成视频。模板是一个接收
RenderContext
并返回HTML字符串的函数。渲染器会逐帧调用该函数,同时更新时间参数。
  • sceneProgress
    (0到1):控制场景内的动画进度
  • sceneTimeSeconds
    :提供基于阶段计时的已流逝时间
  • ctx.std
    :提供补间、数学计算和颜色处理工具
  • ctx.data
    :承载模板数据(与默认值合并)
模板可通过CLI命令(
superimg render
)或服务端API(
renderVideo
loadTemplate
)渲染为MP4格式。

Template Authoring

模板编写

defineTemplate

defineTemplate

typescript
import { defineTemplate } from "superimg";

export default defineTemplate({
  defaults: {
    title: "Hello, SuperImg!",
    subtitle: "Create stunning videos from code",
    accentColor: "#667eea",
  },

  config: {
    width: 1920,
    height: 1080,
    fps: 30,
    durationSeconds: 4,
    fonts: ["Space+Grotesk:wght@400;700"],
  },

  render(ctx) {
    const { std, sceneTimeSeconds: time, width, height, isPortrait, data } = ctx;
    const { title, subtitle, accentColor } = data;

    // Responsive sizing
    const titleSize = isPortrait ? 64 : 88;
    const subtitleSize = isPortrait ? 22 : 28;

    // Phase timing: Enter 0-1.5s | Hold 1.5-3s | Exit 3-4s
    const enterProgress = std.math.clamp(time / 1.5, 0, 1);
    const exitProgress = std.math.clamp((time - 3.0) / 1.0, 0, 1);
    // Animate title
    const titleOpacity = std.tween(0, 1, enterProgress, "easeOutCubic") * (1 - exitProgress);
    const titleY = std.tween(40, 0, enterProgress, "easeOutCubic");

    // Staggered subtitle (+0.3s delay)
    const subtitleEnter = std.math.clamp((time - 0.3) / 1.5, 0, 1);
    const subtitleOpacity = std.tween(0, 0.8, subtitleEnter, "easeOutCubic") * (1 - exitProgress);
    const subtitleY = std.tween(30, 0, subtitleEnter, "easeOutCubic");

    // Accent line
    const lineEnter = std.math.clamp((time - 0.5) / 1.0, 0, 1);
    const lineWidth = std.tween(0, 100, lineEnter, "easeOutCubic") * (1 - exitProgress);
    const lineColor = std.color.alpha(accentColor, 0.8 * (1 - exitProgress));

    return `
      <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
          width: ${width}px;
          height: ${height}px;
          display: flex;
          align-items: center;
          justify-content: center;
          background: #0f0f23;
          font-family: system-ui, sans-serif;
          overflow: hidden;
        }
        .title {
          font-size: ${titleSize}px;
          font-weight: 700;
          color: ${accentColor};
          opacity: ${titleOpacity};
          transform: translateY(${titleY}px);
          margin: 24px 0 12px;
        }
        .subtitle {
          font-size: ${subtitleSize}px;
          color: white;
          opacity: ${subtitleOpacity};
          transform: translateY(${subtitleY}px);
          margin-bottom: 24px;
        }
        .accent-line {
          width: ${lineWidth}%;
          max-width: 500px;
          height: 2px;
          background: ${lineColor};
          margin: 0 auto;
        }
      </style>
      <div style="text-align: center">
        <div class="accent-line"></div>
        <h1 class="title">${title}</h1>
        <p class="subtitle">${subtitle}</p>
        <div class="accent-line"></div>
      </div>
    `;
  },
});
typescript
import { defineTemplate } from "superimg";

export default defineTemplate({
  defaults: {
    title: "Hello, SuperImg!",
    subtitle: "Create stunning videos from code",
    accentColor: "#667eea",
  },

  config: {
    width: 1920,
    height: 1080,
    fps: 30,
    durationSeconds: 4,
    fonts: ["Space+Grotesk:wght@400;700"],
  },

  render(ctx) {
    const { std, sceneTimeSeconds: time, width, height, isPortrait, data } = ctx;
    const { title, subtitle, accentColor } = data;

    // Responsive sizing
    const titleSize = isPortrait ? 64 : 88;
    const subtitleSize = isPortrait ? 22 : 28;

    // Phase timing: Enter 0-1.5s | Hold 1.5-3s | Exit 3-4s
    const enterProgress = std.math.clamp(time / 1.5, 0, 1);
    const exitProgress = std.math.clamp((time - 3.0) / 1.0, 0, 1);
    // Animate title
    const titleOpacity = std.tween(0, 1, enterProgress, "easeOutCubic") * (1 - exitProgress);
    const titleY = std.tween(40, 0, enterProgress, "easeOutCubic");

    // Staggered subtitle (+0.3s delay)
    const subtitleEnter = std.math.clamp((time - 0.3) / 1.5, 0, 1);
    const subtitleOpacity = std.tween(0, 0.8, subtitleEnter, "easeOutCubic") * (1 - exitProgress);
    const subtitleY = std.tween(30, 0, subtitleEnter, "easeOutCubic");

    // Accent line
    const lineEnter = std.math.clamp((time - 0.5) / 1.0, 0, 1);
    const lineWidth = std.tween(0, 100, lineEnter, "easeOutCubic") * (1 - exitProgress);
    const lineColor = std.color.alpha(accentColor, 0.8 * (1 - exitProgress));

    return `
      <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
          width: ${width}px;
          height: ${height}px;
          display: flex;
          align-items: center;
          justify-content: center;
          background: #0f0f23;
          font-family: system-ui, sans-serif;
          overflow: hidden;
        }
        .title {
          font-size: ${titleSize}px;
          font-weight: 700;
          color: ${accentColor};
          opacity: ${titleOpacity};
          transform: translateY(${titleY}px);
          margin: 24px 0 12px;
        }
        .subtitle {
          font-size: ${subtitleSize}px;
          color: white;
          opacity: ${subtitleOpacity};
          transform: translateY(${subtitleY}px);
          margin-bottom: 24px;
        }
        .accent-line {
          width: ${lineWidth}%;
          max-width: 500px;
          height: 2px;
          background: ${lineColor};
          margin: 0 auto;
        }
      </style>
      <div style="text-align: center">
        <div class="accent-line"></div>
        <h1 class="title">${title}</h1>
        <p class="subtitle">${subtitle}</p>
        <div class="accent-line"></div>
      </div>
    `;
  },
});

RenderContext Key Fields

RenderContext 核心字段

typescript
interface RenderContext<TData> {
  std: Stdlib;                    // Tween, math, color utilities

  // Scene timing (use these for animation)
  sceneProgress: number;          // 0-1 progress through current scene
  sceneTimeSeconds: number;       // Elapsed seconds in current scene
  sceneDurationSeconds: number;   // Total scene duration

  // Canvas
  width: number;                  // Canvas width in pixels
  height: number;                 // Canvas height in pixels
  isPortrait: boolean;            // height > width
  isLandscape: boolean;           // width > height

  // Data
  data: TData;                    // Scene-specific data (merged with defaults)
  // Global timing (rarely needed in templates)
  globalProgress: number;         // 0-1 progress through entire video
  globalTimeSeconds: number;
  fps: number;
}
typescript
interface RenderContext<TData> {
  std: Stdlib;                    // Tween, math, color utilities

  // Scene timing (use these for animation)
  sceneProgress: number;          // 0-1 progress through current scene
  sceneTimeSeconds: number;       // Elapsed seconds in current scene
  sceneDurationSeconds: number;   // Total scene duration

  // Canvas
  width: number;                  // Canvas width in pixels
  height: number;                 // Canvas height in pixels
  isPortrait: boolean;            // height > width
  isLandscape: boolean;           // width > height

  // Data
  data: TData;                    // Scene-specific data (merged with defaults)
  // Global timing (rarely needed in templates)
  globalProgress: number;         // 0-1 progress through entire video
  globalTimeSeconds: number;
  fps: number;
}

Core Stdlib

核心标准库

Tween (canonical animation primitive):
std.tween(from, to, progress)                        // Linear interpolation
std.tween(from, to, progress, "easeOutCubic")       // Eased interpolation
std.tween(from, to, progress, { start, end, easing }) // Windowed animation
Math:
std.math.clamp(value, min, max)      // Restrict to range
std.math.map(val, inMin, inMax, outMin, outMax)  // Remap range
Color:
std.color.alpha(color, opacity)      // Add transparency: alpha('#F00', 0.5)
std.color.mix(color1, color2, t)     // Blend two colors
std.color.lighten(color, amount)     // Lighten by percentage
std.color.darken(color, amount)      // Darken by percentage
std.color.hsl(h, s, l)              // Create HSL string
补间动画(标准动画原语):
std.tween(from, to, progress)                        // Linear interpolation
std.tween(from, to, progress, "easeOutCubic")       // Eased interpolation
std.tween(from, to, progress, { start, end, easing }) // Windowed animation
数学工具:
std.math.clamp(value, min, max)      // Restrict to range
std.math.map(val, inMin, inMax, outMin, outMax)  // Remap range
颜色工具:
std.color.alpha(color, opacity)      // Add transparency: alpha('#F00', 0.5)
std.color.mix(color1, color2, t)     // Blend two colors
std.color.lighten(color, amount)     // Lighten by percentage
std.color.darken(color, amount)      // Darken by percentage
std.color.hsl(h, s, l)              // Create HSL string

Phase Timing Pattern

阶段计时模式

For multi-phase animations, use
sceneTimeSeconds
with
clamp
:
typescript
render(ctx) {
  const { std, sceneTimeSeconds: time } = ctx;

  // Enter: 0-1s | Hold: 1-3s | Exit: 3-4s
  const enterProgress = std.math.clamp(time / 1.0, 0, 1);
  const exitProgress = std.math.clamp((time - 3.0) / 1.0, 0, 1);
  const eased = std.tween(0, 1, enterProgress, "easeOutCubic");
  const opacity = eased * (1 - exitProgress);
  const y = std.tween(50, 0, enterProgress, "easeOutCubic");
  // ...
}
对于多阶段动画,可结合
sceneTimeSeconds
clamp
函数使用:
typescript
render(ctx) {
  const { std, sceneTimeSeconds: time } = ctx;

  // Enter: 0-1s | Hold: 1-3s | Exit: 3-4s
  const enterProgress = std.math.clamp(time / 1.0, 0, 1);
  const exitProgress = std.math.clamp((time - 3.0) / 1.0, 0, 1);
  const eased = std.tween(0, 1, enterProgress, "easeOutCubic");
  const opacity = eased * (1 - exitProgress);
  const y = std.tween(50, 0, enterProgress, "easeOutCubic");
  // ...
}

Responsive Sizing

响应式尺寸适配

Use
isPortrait
/
isLandscape
to adapt layout:
typescript
const fontSize = ctx.isPortrait ? 48 : 72;
const layout = ctx.isPortrait ? "column" : "row";
使用
isPortrait
/
isLandscape
来适配布局:
typescript
const fontSize = ctx.isPortrait ? 48 : 72;
const layout = ctx.isPortrait ? "column" : "row";

Rendering

渲染

CLI Rendering

CLI渲染

bash
undefined
bash
undefined

Scaffold a new project (detects package manager automatically)

Scaffold a new project (detects package manager automatically)

superimg init my-project superimg init my-project --pm pnpm # override package manager superimg init --add # Add videos/ to existing project
superimg init my-project superimg init my-project --pm pnpm # override package manager superimg init --add # Add videos/ to existing project

Dev server with live preview (bare name resolves to videos/intro.ts)

Dev server with live preview (bare name resolves to videos/intro.ts)

superimg dev intro
superimg dev intro

Render to video

Render to video

superimg render videos/intro.ts -o output.mp4
superimg render videos/intro.ts -o output.mp4

With options

With options

superimg render videos/intro.ts -o output.mp4 --width 1080 --height 1920 --fps 60
undefined
superimg render videos/intro.ts -o output.mp4 --width 1080 --height 1920 --fps 60
undefined

Programmatic Rendering (Server)

程序化渲染(服务端)

typescript
import { renderVideo, loadTemplate } from "superimg/server";

// Load template from file
const template = await loadTemplate("videos/intro.ts");

// Render to MP4
await renderVideo("videos/intro.ts", {
  outputPath: "output.mp4",
  width: 1920,
  height: 1080,
});
For low-level control, use
createRenderPlan
,
executeRenderPlan
, and
PlaywrightEngine
from
superimg/server
.
typescript
import { renderVideo, loadTemplate } from "superimg/server";

// Load template from file
const template = await loadTemplate("videos/intro.ts");

// Render to MP4
await renderVideo("videos/intro.ts", {
  outputPath: "output.mp4",
  width: 1920,
  height: 1080,
});
如需底层控制,可使用
superimg/server
中的
createRenderPlan
executeRenderPlan
PlaywrightEngine

Gotchas

注意事项

  1. HTML strings, not JSX. Templates return template literal strings, not React/JSX. Use
    ${}
    interpolation for dynamic values.
  2. Pure render function. The
    render
    function must be pure — no side effects, no DOM manipulation, no global state. It receives context and returns an HTML string.
  3. Use
    sceneProgress
    , not
    globalProgress
    .
    For animation within a scene, always use
    sceneProgress
    (0-1 within the current scene).
    globalProgress
    spans the entire video and is rarely useful in templates.
  4. Set root dimensions to canvas size. Style the root element (typically
    body
    or an outer
    div
    ) to
    width: ${width}px; height: ${height}px
    so content fills the frame.
  5. Import from the right path. Templates import from
    "superimg"
    (for
    defineTemplate
    , types). Server/rendering imports from
    "superimg/server"
    .
  6. Data merges with defaults. When a template has
    defaults
    , scene
    data
    is merged:
    { ...defaults, ...data }
    . You only need to pass overrides.
  7. Use
    config.fonts
    for Google Fonts.
    Declare fonts in
    config.fonts
    (e.g.,
    ["Space+Grotesk:wght@400;700"]
    ) instead of adding
    @import url(...)
    in your render HTML. The library automatically injects the
    <link>
    tags and waits for fonts to load before capturing each frame.
  1. 使用HTML字符串,而非JSX:模板需返回模板字面量字符串,而非React/JSX。使用
    ${}
    语法插入动态值。
  2. 纯渲染函数
    render
    函数必须是纯函数——无副作用、无DOM操作、无全局状态。它接收上下文参数并返回HTML字符串。
  3. 使用
    sceneProgress
    而非
    globalProgress
    :场景内的动画应始终使用
    sceneProgress
    (当前场景内0到1的进度)。
    globalProgress
    代表整个视频的进度,在模板中很少用到。
  4. 设置根元素尺寸为画布大小:将根元素(通常是
    body
    或外层
    div
    )的样式设置为
    width: ${width}px; height: ${height}px
    ,确保内容填满整个帧。
  5. 正确导入路径:模板需从
    "superimg"
    导入(用于
    defineTemplate
    和类型定义)。服务端/渲染相关代码需从
    "superimg/server"
    导入。
  6. 数据与默认值合并:当模板定义了
    defaults
    时,场景
    data
    会与默认值合并:
    { ...defaults, ...data }
    。只需传入需要覆盖的参数即可。
  7. 通过
    config.fonts
    使用谷歌字体
    :在
    config.fonts
    中声明字体(例如
    ["Space+Grotesk:wght@400;700"]
    ),而非在渲染的HTML中添加
    @import url(...)
    。该库会自动注入
    <link>
    标签,并在捕获每帧前等待字体加载完成。