Motion Frames — 基于帧的动效设计系统 是做什么的?
无需视频软件即可制作专业水准运动图形的完整方法:整个动画是一个 HTML 页面,其场景是帧号的纯函数,由无头 Chromium 逐帧“拍摄”,并通过 ffmpeg 合成为 MP4。一切都是确定性的:相同输入,相同像素——可以并行渲染、从任意帧恢复、以 1/60 秒的精度同步音频和画面。
1. 不可违反的规则:帧的纯函数
- 整个场景存在于
renderFrame(n)中,其中n是帧号(60fps)。 - 禁止使用时钟(
Date.now、performance.now、通过requestAnimationFrame累积的状态),禁止 无种子的随机性:随机性只能通过索引/帧的确定性hash(i)实现。 - 运动 = 从事件开始起经过的时间的闭式公式:
- 解析弹簧(欠阻尼):
spring(t, freq, damp)带过冲——是所有行为的默认物理模型(入场、弹跳、堆叠)。 - 命名缓动函数:
easeOutCubic、easeInOutCubic、easeOutBack。 - 时间段:
seg(n, a, b)= 从帧区间 [a, b] 内的进度 0→1;clamp、lerp用于插值任何量(位置、颜色、几何变形)。
- 解析弹簧(欠阻尼):
- 默认格式:1080x1920,60fps。时长由内容决定(快速测试:8s = 480 帧)。
2. 流水线(此技能所在的 pipeline/ 目录)
| 文件 | 说明 |
|---|---|
pipeline/template.html | 包含助手函数(T、seg、spring、缓动函数、hash)和 renderFrame(n) 骨架的基础页面 |
pipeline/render.mjs | 无依赖的 CDP 渲染器:node render.mjs <page.html> <dir> <num_frames> 1080 1920 [start_frame] |
pipeline/preview.mjs | 仅渲染选定的帧为 PNG:node preview.mjs <page.html> <dir> "0,60,160,300" <total> 1080 1920 |
pipeline/sfx_kit.py | 与帧号绑定的合成音效工具包(numpy/scipy) |
- Chromium:渲染器首先查找
CHROME_BIN,然后查找脚本旁边的chrome-headless-shell,最后查找工作区的已知路径。并非所有 Chromium 都适用:带有域/WebGL 策略的构建(例如 Hatch 管理的 Chromium)拒绝独立运行——请使用独立的 chrome-headless-shell。 - 速度:
FMT=jpeg(质量 90)比 PNG 快约 2 倍,且结果无法区分。PNG 仅用于预览。 - 分片:长视频通过 2 个并行进程渲染(使用
start_frame参数处理区间的一半),每个进程都有自己的PORT(CDP)。在约 8GB 内存的机器上,最多 2 个并行——3 个会耗尽 RAM。 - 合成:
ffmpeg -framerate 60 -i dir/f%04d.jpg -i audio.wav -c:v libx264 -pix_fmt yuv420p -crf 18 -c:a aac -b:a 192k -movflags +faststart out.mp4。 - 本地字体和图像:通过相对于 HTML 的路径引用(通过
file://生效)。品牌字体:下载.woff2文件并生成本地@font-faceCSS——不要在渲染时依赖网络。场景中使用的图像:在页面主体中包含一个隐藏的<img>预加载标签,否则前几帧将没有图像。
3. 制作流程(按此顺序)
- 脚本和参考:定义每个场景的台词。逐帧分析参考(使用 ffmpeg 提取 contact sheet),并记录其语法——不要复制表面。
- 先处理音频(如果有旁白):在决定剪辑之前检测静音(
ffmpeg -af silencedetect);使用 faster-whisper(small、word_timestamps=True,将音频作为从 16k WAV 读取的 numpy 数组传递——faster-whisper 的内部解码在某些 PyAV 版本中会出错)进行转录,并将关键字的帧时间转换为帧地标(second × 60)。切勿凭空捏造语音持续时间。 - 品牌侦察(产品视频):获取真实品牌标识——下载网站的 HTML/CSS,并提取 Logo(图像文件)、调色板(CSS 中最常见的十六进制颜色)和字体。在视频中使用官方 Logo 和字体。
- 按场景设计:每个场景一个函数(
s1(n)、s2(n)……),边界在语音地标处。每个场景在诞生时其主要事件就已同步。 - 必须预览:使用
preview.mjs生成包含 15–30 帧的 contact sheet,以检查节奏和构图——并且务必以全尺寸检查 2–3 个关键帧:contact sheet 具有欺骗性(隐藏文本碰撞、宽度溢出和小细节)。 - 按微事件添加音效(第 8 节),使用与动画相同的帧号。
- 分片渲染 + 合成 + QA:从最终 MP4(而不是分散的帧)中提取帧,并在交付前检查。
4. 运动语言(区分专业与平庸的关键)
- 电影感开场:第一秒是一个标题——大型排版通过弹簧和摄像机运动进入,背景具有深度。没有静态屏幕。
- 摄像机从不停止:所有镜头中缓慢的缩放漂移(1→1.03–1.09);通过运动(各层之间的 dolly/zoom/pan 视差)伪装剪辑。
- 密度:每 0.5–1.5 秒发生一个微事件。如果某个部分超过 2 秒没有任何事情发生,那就是错误的。
- 处处有物理:带有过冲的弹簧、掉落和弹跳的挤压与拉伸、主要运动前的预期。默认禁止线性运动。
- 转场是事件,而非剪辑:对象变形(药丸变成条形,条形变成卡片)、带模糊的缩放穿越、或缩放冲击(在场景边界处缩放跨越 1→1.09 并伴有约 18 帧的交叉淡入淡出)。
- 精简的调色板:一个色系的背景 + 一种带辉光的高亮颜色;粗体排版;大量留白。
5. 背景:从不扁平,始终保持活力
禁止纯色黑背景(或任何纯色背景)——背景有它自己的生命、流动以及背景/中间/前景之间的视差。三个经过验证的配方:
- 活力工作室(深色宣传):使用高亮颜色的渐变,2 个缓慢移动的模糊颜色斑点,顶部的聚光灯,确定性哈希的浮动散景粒子,带透视点网格的地板 + 发光地平线 + 中心元素下方的反射。
- 液态极光(连续场景):3–4 条拉长的径向渐变带,
mix-blend-mode: screen,强模糊,以利萨如模式缓慢移动并旋转;两种调色板在命令更改场景“颜色”时进行交叉淡入淡出;顶部有粒子尘埃。 - 品牌冰面(浅色动态排版):品牌颜色的浅色渐变,薄荷丝绸流动(与极光相同技术,但为浅色),微妙的点网格和尘埃——干净,但有呼吸感。
6. 真正的 3D(下足功夫——这是视频最常落后于参考的地方)
- CSS
perspective+rotateX/rotateY应用于手机、卡片和仪表盘;元素漂浮,带有响应倾斜的动态阴影。 - 真实视差:在所有缩放/平移中,背景、中间和前景以不同速度移动。
- CSS 中的 3D 立方体和盒子:每个面是一个定位的 div,使用
translateZ = 宽度/高度的一半(搞错这个会打开边缘)。 - 上升的等距楼梯/块、带深度的堆叠、玻璃(当后面背景简单时使用
backdrop-filter)和扫过的镜面高光。 - 经验法则:如果场景可以描述为“文本 + 平面矩形”,则缺少 3D。添加倾斜、层或深度,直到它看起来像是被拍摄的,而不是被绘制的。
7. 四种语法(逐镜头结构)
A. 电影/变形(开场和风格测试)
电影感标题 → 沿虚线轨迹挤压和拉伸的对象 → 摄像机下降,对象进行变形(圆形→药丸→屏幕)→ 旋转的 3D 元素,表面缩放 → 动态排版,手绘圆圈 → 玻璃面板,带弹簧动画的图表 → 结尾回到起始点(如果需要,完美循环)。
B. 高级应用宣传(参考 Opal/Qwix)
- 动态环境背景 + 聚光灯;一种带辉光/泛光的高亮颜色。
- 开场:UI/手机在 3D 透视中漂浮,带有拉焦的 dolly-in(开始模糊,然后聚焦)。
- 摄像机沉入一个细节(图表条),该细节变成下一个场景。
- 玻璃药丸带有图标 + 数字计数(easeOut)+ 标签;边缘带有行进光弧。
- 聚合:辅助线从药丸中画出,汇聚成一个带有 ▲ 的巨大数字。
- 应用图标变形 + 粒子爆炸。
- 应用页面 + 巨大的 CTA 药丸按钮,带有光扫描。
- Logo 揭示:符号画出(SVG dash)+ 文字标记 + 小 URL。
C. 连续交互场景(参考 "Built with Claude")
单个场景,无剪辑,在液态极光上:符号画出 → 命令药丸进入 → 光标移动并点击 一个药丸 → 药丸变形为提示栏 → 提示逐字打出(闪烁光标,最后一个词高亮)→ 提示栏变形为结果(内部构建的卡片:环、条、播放、进度 %)→ 第二个键入的命令改变整个背景颜色 → 结尾带有符号 + 署名。故事是“某人正在使用产品”。
D. 浅色动态排版(葡萄牙语排版广告)
品牌冰面背景;品牌字体的粗体排版为主角,每词在语音节拍上,关键字使用高亮颜色,彩色鬼影阴影和手绘涂鸦(划线、圆圈、嗖嗖声——SVG dash)。穿插讲述故事的真实 UI:带有物理堆叠的通知、条形对比图、Logo 周围的轨道节点系统、对话气泡聊天、带图表的卡片、巨大计数器。场景边界通过缩放冲击;中间有暗色反拍节拍("而且不止如此!");三击标语带图标;结尾为 Logo + 光标点击的 CTA + URL。
8. 声音:基于微事件的音效
- 音轨在
sfx_kit.py中合成:每个声音事件使用与视觉事件相同的帧号。 - 词汇表:
pop(点击、卡片、大字)、tick(打字、条、计数器)、whoosh(转场和变形)、shimmer(确认、揭示)、pad(底层和谐铺垫)。 - 密度与视频相同:如果有视觉微事件,就有声音。在旁白下,将背景音乐混音到约 0.5 音量,让声音主导。
- 永远不要生成或替换用户的语音:旁白始终是用户自己的,保持原样(仅在使用分析后的音频并在剪辑后重新同步的情况下才进行静音剪辑)。
9. 反通用检查清单(交付前检查)
- 排版是主角还是仅仅是 UI 标签?(必须是主角)
- 所有场景中每 0.5–1.5 秒是否有微事件?
- 是否有东西堆叠、掉落或物理碰撞——还是所有东西都孤立漂浮?
- 是否存在交互(光标点击、键入提示、正在使用的产品)?
- 是否有手绘涂鸦层(圆圈、下划线、箭头、划线)?
- 至少一个转场是对象变形,而不仅仅是缩放?
- 背景是否具有自身流动性以及层间视差?
- UI 是否包含讲述故事的“真实”内容(数值、对话、名称)而不是抽象的占位符?
- 是否有轨道系统、轨迹文本或其他“签名”元素?
- 3D 是否通过第 6 节的测试?
- 每个微事件是否有音效?
10. 商业事实(不要编造数字)
- 变成商业承诺的数字(费率、通过率、价格)不能编造:如果品牌未公布,请使用不带数字的视觉对比(高条 × 低条)、作为运动效果的成交量/增长计数器以及定性语言("高"、"低很多")。
- 关于产品的不确定事实应在编写脚本前标记,切勿在渲染后。
11. 已经犯过的错误(不要重复)
- Contact sheet 具有欺骗性:缩略图隐藏文本碰撞和宽度溢出。始终以全尺寸验证关键帧。
- SVG 中的重复属性:同一元素上有两个
stroke-dasharray——第一个获胜,draw-on 动画悄然失效。一个元素一个属性。 - 硬编码的场景索引:返回固定场景(
return 5)的函数在场景数量改变时会破坏最后一个场景。从边界数组中导出。 - 3D 立方体开口:
translateZ错误的表面(≠ 尺寸的一半)会在边缘留下缝隙。 - 3 个并行分片 = 小机器上内存溢出。最多 2 个。
- 系统 Chromium 可能不适用(域/WebGL 策略):使用独立的 chrome-headless-shell。
- 无预加载的图像仅在初始帧之后出现:隐藏的
<img>在主体中解决此问题。 - “几乎正确”的场景:测量大字体中宽文本——118px 的标语可能超过 1080px 并严重断裂;在大的预览中检查实际宽度。
12. 示例项目(此系统的真实作品)
~/workspace/motion-frames/teste_8s.mp4— 语法 A:球/变形/立方体/动态排版循环,8 秒。~/workspace/iphone18/iphone18_pro.mp4— 逐字同步旁白(53 秒),CSS 3D iPhone。~/workspace/kalo/kalo_app_v2.mp4— 语法 B,活力工作室背景 + AI 餐食扫描场景,15 秒。~/workspace/kalo/feito_com_muse.mp4— 语法 C:连续场景、光标、键入提示、变形、带交叉淡入淡出的极光,14 秒。~/workspace/turtle/turtlepags.mp4— 完整语法 D,带旁白、真实品牌标识(来自网站的 Logo/调色板/字体),53 秒。