mcpField
R
remotion-keyframes

remotion-keyframes

github.com/phamthanhnghia创意与媒体362 次安装14 周前更新
代码仓库

关于 remotion-keyframes

remotion-keyframes 是用于 Remotion 动画的技能,指导创建可按帧确定性重现的关键帧,并通过 Studio 检查动画状态。它强调主体连续性、可见姿态和最终帧验证。

主要功能

  1. 01依据当前帧计算动画值
  2. 02使用 spring 与 interpolate 制作关键帧
  3. 03按帧延迟编排多项动画
  4. 04通过 Studio 检查全时间线
  5. 05避免渲染关键路径中的非确定性动画

适用场景

  1. 01制作入场、渐变及多段动画
  2. 02实现文字逐词显示或 SVG 描边动画
  3. 03检查动画首帧、各姿态和最终帧

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/phamthanhnghia/remotion-agent-skills --skill remotion-keyframes

安装后在 Claude Code 中输入/remotion-keyframes确认已加载。

详细介绍

Remotion 关键帧

关键帧是一份姿态契约:状态清晰可见、主体身份连续、帧结果确定、像素经过验证。

需要通用场景方案时,使用 remotion-animation。需要完整命令文档时,使用 remotion-cli。

流程

  1. 确定动画主体、可见状态、最终状态,以及动画原语(spring 或 interpolate)。
  2. 选择能够证明提示词的最精简机制。
  3. 编写帧结果确定的关键帧。所有数值均由 useCurrentFrame() 得出。
  4. 使用 npx remotion studio 验证——拖动浏览完整时间轴,检查首帧、所有姿态和末帧。
  5. 如果验证失败,修正源关键帧,并在渲染前重新验证。

契约

  • 明确指出移动的主体。
  • 明确列出证明预期运动所需的姿态,包括最终状态。
  • 为可见通道设置关键帧,而非隐藏的辅助状态。
  • 需要保持连续性时,保留对象身份。
  • 可读或具有语义的状态应保持足够长的时间,以便看清(文本至少 10 帧,图标至少 6 帧)。
  • 末帧是动画的一部分,不是收尾处理。
  • 除非有要求,否则不要重置为静止状态。
  • 除非有要求,否则不要以黑屏结束。

运行时规则

interpolate() 和 spring():

  • 所有数值都必须由 frame 同步计算——不要在 interpolate/spring 调用中使用异步逻辑
  • 使用 extrapolateLeft: 'clamp' 和 extrapolateRight: 'clamp',防止数值超出预期范围
  • spring({frame, fps, config})——frame 必须是 Remotion 的 useCurrentFrame() 值,而不是由 Date.now() 推导出的值
  • 对于入场动画,偏移 spring:spring({frame: frame - delay, fps, config});使用 Math.max(0, frame - delay),避免负帧驱动 spring

渲染关键动画中绝不要使用:

  • Date.now() 或 performance.now()
  • 未设种子的 Math.random()(改用 Remotion 的 random(seed))
  • CSS 过渡或 CSS 动画(无法拖动时间轴定位)
  • requestAnimationFrame
  • 渲染路径中的 setTimeout / setInterval
  • 用于视觉属性值的 useState / useEffect

Spring 配置参考

import {spring, useCurrentFrame, useVideoConfig} from 'remotion';

const frame = useCurrentFrame();
const {fps} = useVideoConfig();

// Snappy entrance (default feel)
const scale = spring({frame, fps, config: {damping: 200}});

// Bouncy entrance
const bounce = spring({frame, fps, config: {damping: 8, stiffness: 300}});

// Very smooth, slow
const smooth = spring({frame, fps, config: {damping: 30, stiffness: 80, mass: 2}});

// Sharp exit (reverse spring — animate from 1 to 0)
const {durationInFrames} = useVideoConfig();
const exitFrame = durationInFrames - frame;
const exitScale = spring({frame: exitFrame, fps, config: {damping: 200}});
const scaleValue = 1 - (1 - exitScale) ; // goes from 1 down to 0

Interpolate 方案

import {interpolate} from 'remotion';

// Fade in over 20 frames
const opacity = interpolate(frame, [0, 20], [0, 1], {extrapolateRight: 'clamp'});

// Slide in from left
const x = interpolate(frame, [0, 30], [-300, 0], {
  extrapolateLeft: 'clamp',
  extrapolateRight: 'clamp',
});

// Multi-stop color shift
const r = interpolate(frame, [0, 30, 60], [255, 100, 0], {extrapolateRight: 'clamp'});
const g = interpolate(frame, [0, 30, 60], [0, 150, 255], {extrapolateRight: 'clamp'});

// Scale pop
const scale = interpolate(frame, [0, 8, 12, 18], [0, 1.15, 0.95, 1], {
  extrapolateLeft: 'clamp',
  extrapolateRight: 'clamp',
});

通道

优先使用合成器/视觉通道:transform: translateX/Y/Z、transform: scale/rotate/skew、opacity、borderRadius、CSS 变量、clipPath、SVG strokeDashoffset。

避免使用布局通道:top/left/right/bottom、width/height、margin/padding——它们会触发重排、速度较慢,并且渲染器并行采样帧时会产生错误结果。

交错模式

const ITEM_COUNT = 5;
const STAGGER_FRAMES = 8; // delay between each item

const items = Array.from({length: ITEM_COUNT}, (_, i) => {
  const delayedFrame = Math.max(0, frame - i * STAGGER_FRAMES);
  const itemSpring = spring({frame: delayedFrame, fps, config: {damping: 200}});
  return {opacity: itemSpring, y: (1 - itemSpring) * 40};
});

文本动画模式

const words = 'Hello Remotion World'.split(' ');

const WordReveal = () => {
  const frame = useCurrentFrame();
  const {fps} = useVideoConfig();

  return (
    <div style={{display: 'flex', gap: 12}}>
      {words.map((word, i) => {
        const delayedFrame = Math.max(0, frame - i * 6);
        const progress = spring({frame: delayedFrame, fps, config: {damping: 200}});
        return (
          <span
            key={i}
            style={{
              opacity: progress,
              transform: `translateY(${(1 - progress) * 20}px)`,
              display: 'inline-block',
            }}
          >
            {word}
          </span>
        );
      })}
    </div>
  );
};

SVG 描边绘制

// SVG stroke-dasharray draw-on effect
const pathLength = 500; // measure with path.getTotalLength() in development
const drawProgress = interpolate(frame, [0, 60], [0, 1], {extrapolateRight: 'clamp'});

<path
  d="M 0 0 L 300 200"
  stroke="white"
  strokeWidth={4}
  fill="none"
  strokeDasharray={pathLength}
  strokeDashoffset={pathLength * (1 - drawProgress)}
/>

3D 深度(CSS 透视)

// CSS perspective 3D scene
<div style={{perspective: 800, perspectiveOrigin: '50% 50%'}}>
  <div
    style={{
      transformStyle: 'preserve-3d',
      transform: `rotateY(${interpolate(frame, [0, 60], [45, 0], {extrapolateRight: 'clamp'})}deg)`,
    }}
  >
    {/* 3D content */}
  </div>
</div>

机制选择

需求机制
自然减速的入场spring()
定时数值过渡interpolate()
多段序列带多个输入/输出节点的 interpolate()
多个项目按顺序入场交错:在 spring() 中使用索引化的 frame - i * delay
文本逐词显现按单词索引交错使用 spring
描边延伸或绘制通过 interpolate() 设置 strokeDashoffset
形状显现边界通过 interpolate() 设置 clipPath
3D 深度场景通过 interpolate() 设置 CSS perspective + rotateX/Y/Z

错误处理

问题修复方法
某些帧的数值发生跳变检查传给 spring() 的 frame 是否为负数——使用 Math.max(0, frame - delay) 将其限制为非负数
动画无法正确拖动定位确认没有使用 useState/CSS 动画驱动数值
开头或结尾出现黑帧检查入场时是否设置了 extrapolateLeft: 'clamp',以及出场时是否设置了 extrapolateRight: 'clamp'
文本闪烁或显示不一致确保 random() 的种子是稳定的字符串,而不是 Math.random()
Spring 超调并移出屏幕增大 damping(200 = 不超调,8 = 大幅弹跳)

完成

在 Studio 中验证:拖动浏览首帧、所有可见姿态和末帧。确认运动由主体自身驱动、没有调试叠加层,且首帧没有闪烁。

评论

支持 Markdown