比特之外

Remotion 教程 10:用 Skia 做高级 2D 图形和光效

DOM 和 CSS 很适合做图文排版,但遇到复杂光效、路径、粒子、手绘风和动态图形时,会越来越难维护。Skia 适合解决这类高级 2D 绘制问题。

推荐组合:

@remotion/skia
@shopify/react-native-skia

Skia 负责绘制,Remotion 负责时间。

Skia 适合什么视频

优先考虑 Skia 的场景:

  • 粒子和光效。
  • 复杂路径动画。
  • 手绘风线条。
  • 动态海报和动态图形。
  • 大量形状叠加。
  • DOM 难以高效表达的 2D 画面。

不建议一开始就用 Skia 做所有东西。标题、字幕、卡片、普通图文排版,用 DOM 更直接。

动画还是由 frame 控制

Skia 负责绘制,但动画数值仍然来自 Remotion:

const frame = useCurrentFrame();
const radius = interpolate(frame, [0, 75, 150], [34, 108, 34]);
const x = interpolate(frame, [0, 150], [140, 500]);

这段代码表示:

圆的半径在 34 -> 108 -> 34 之间变化
圆的 x 坐标从 140 移动到 500

Skia 不需要自己维护时间。每一帧的圆心和半径都由 Remotion 算出来。

完整最小示例

下面是一个 Skia 光效画布:背景是渐变,前景有几个叠加发光圆。

import React from "react";
import {
  Canvas,
  Circle,
  Group,
  LinearGradient,
  Rect,
  vec,
} from "@shopify/react-native-skia";
import { interpolate, useCurrentFrame } from "remotion";
 
export const SkiaArtwork: React.FC = () => {
  const frame = useCurrentFrame();
  const radius = interpolate(frame, [0, 75, 150], [34, 108, 34]);
  const x = interpolate(frame, [0, 150], [140, 500]);
 
  return (
    <Canvas style={{ width: 640, height: 460 }}>
      <Rect x={0} y={0} width={640} height={460}>
        <LinearGradient
          start={vec(0, 0)}
          end={vec(640, 460)}
          colors={["#111827", "#312e81", "#0f766e"]}
        />
      </Rect>
 
      <Group blendMode="screen">
        <Circle cx={x} cy={220} r={radius} color="#38bdf8" opacity={0.72} />
        <Circle
          cx={640 - x / 2}
          cy={170}
          r={72}
          color="#f59e0b"
          opacity={0.58}
        />
        <Circle cx={250} cy={310} r={54} color="#a78bfa" opacity={0.64} />
      </Group>
    </Canvas>
  );
};

这里的 blendMode="screen" 会让颜色产生更接近光效叠加的视觉效果。

Web 环境要等待 Skia 加载

Skia Web 需要加载 wasm。渲染视频时不能在 Skia 还没准备好时截图,所以需要用 delayRender() 等待:

const { continueRender, delayRender } = useDelayRender();
const renderHandle = useMemo(
  () => delayRender("加载 Skia Web 和示例图形"),
  [delayRender],
);
 
useEffect(() => {
  LoadSkiaWeb({ locateFile: (file) => `/${file}` })
    .then(() => import("./SkiaArtwork"))
    .then((module) => {
      setArtwork(() => module.default);
      window.requestAnimationFrame(() => {
        continueRender(renderHandle);
      });
    });
}, [continueRender, renderHandle]);

这个配置比普通 DOM 组件复杂,所以 Skia 适合在确实需要高级绘制时再引入。

工程注意点

项目里还需要在 remotion.config.ts 启用 Skia:

Config.overrideWebpackConfig((currentConfiguration) => {
  return enableSkia(enableTailwind(currentConfiguration));
});

如果忘了这一步,Skia Web 渲染很容易失败。

示例项目位置

这篇文章对应示例项目里的:

Composition: LessonSkiaEcosystem
文件: src/lessons/SkiaEcosystem.tsx
文件: src/lessons/SkiaArtwork.tsx
配置: remotion.config.ts

运行示例项目后,可以尝试:

  1. radius 的最大值从 108 改成 150。
  2. x 的终点从 500 改成 560。
  3. 修改一个 Circle 的颜色,观察光效变化。

下一篇看 D3:数据视频里,怎么把数据映射和逐帧动画分开。