动画 OG 图像
此技能使用的所有文件都位于 SKILL.md 旁边的专用文件夹内,路径均相对于该文件夹。它不需要其他技能或其他文件夹中的任何内容,因此无需寻找。除非用户指定了其他位置,否则将生成的所有内容保存在当前项目的 out/og-image-animated/ 目录下。
将静态分享卡片制作为短小而平静的揭示动画:
- 空白卡片:仅显示背景和柔和发光。
- 品牌标识和标题从顶部落下(淡入 + 下滑)。
- 应用窗口从底部升起(淡入 + 上滑)并精确稳定在静态卡片上的位置。
- 在最终帧上保持,该帧必须与静态 OG 图像一致。
示例:references/pulse.gif(结果)和 references/pulse-frames.png(四帧:开始、标题进入、应用升起、最终)。
规则
- 从静态卡片开始。 如果项目还没有 OG 卡片组件,请先按照
references/static-card.md构建(真实产品、来自当前站点的消息、真实数据)。动画仅添加运动:相同的标记、相同的最终布局,无新内容。 - 一个组件,两种模式。 向现有卡片组件添加
animatedprop,而不是复制它,以便静态 PNG 和动画永不脱离。如果没有该 prop,则不进行动画。 - 运动,而非特效。 仅使用
opacity+transform: translateY,一个共享的缓出曲线(cubic-bezier(0.16, 1, 0.3, 1))。无弹跳、模糊、缩放弹出、打字机文本或旋转元素。 - 简短。 整个时间线约 2–2.5 秒,然后保持约 1.5 秒。标题必须在约 1.3 秒时完全可读。
- 仅使用 CSS 动画(关键帧 + 带有
bothfill 的animation),卡片内无 JS 驱动的补间或计时器。记录器通过 Web Animations API 寻找 CSS 动画;JS 驱动的运动无法确定性地捕获。
时间线
| 元素 | 关键帧 | 持续时间 | 延迟 |
|---|---|---|---|
| 品牌标识 | 从 opacity: 0; translateY(-48px) 开始 | 900ms | 100ms |
| 标题 | 从 opacity: 0; translateY(-48px) 开始 | 1000ms | 250ms |
| 应用窗口 | 从 opacity: 0; translateY(320px) 开始 | 1300ms | 700ms |
调整距离以适配卡片:应用窗口应从底部边缘下方(约卡片高度的一半)开始,标题则应在其静止位置上方约自身高度处开始。如果有浮动面板,让它最后到达(在窗口开始后 +200ms)。
步骤
1. 发现
- 找到静态卡片组件及其渲染路由(例如
og-image.tsx、/og/<page>),以及public/中的当前 PNG。 - 注意品牌标识、标题和应用窗口的包装器。如果窗口使用
zoom来适配,则对内部包装器进行动画处理,而不是缩放的元素,以保持偏移正确。
2. 添加动画模式
在卡片组件中:
const KEYFRAMES = `
@keyframes og-drop { from { opacity: 0; transform: translateY(-48px); } to { opacity: 1; transform: none; } }
@keyframes og-rise { from { opacity: 0; transform: translateY(320px); } to { opacity: 1; transform: none; } }
`;
const EASE = 'cubic-bezier(0.16, 1, 0.3, 1)';
const enter = (name: string, ms: number, delay: number) => ({ animation: `${name} ${ms}ms ${EASE} ${delay}ms both` });
// <div data-og-frame …>{animated && <style>{KEYFRAMES}</style>} …
// brand: style={animated ? enter('og-drop', 900, 100) : undefined}
// title: style={animated ? enter('og-drop', 1000, 250) : undefined}
// window: <div style={{ zoom }}><div className="h-full" style={animated ? enter('og-rise', 1300, 700) : undefined}>…
将关键帧放在组件中(<style> 标签)或项目的全局 CSS 中;使用 Tailwind v4 时,@theme 内的关键帧仅在匹配的 --animate-* token 被使用时才会发出,因此优先使用组件或普通 CSS。
3. 添加一个播放动画的网页路由
作为静态路由的兄弟路由,例如 /og/<page>-animated(noindex,无应用 chrome):
- 在 1200×630 的框架内渲染
<Card animated />。 - 重新播放以预览:使用每约 4.5 秒更改的
key重新挂载卡片。 ?loop=0一次播放(记录器使用此参数)。先将搜索参数规范化为字符串:像 TanStack 这样的路由器会将?loop=0解析为数字0,如果检查遗漏,则会在录制期间继续重新挂载卡片。
在浏览器中打开并观看几次。检查进入顺序,确保动画开始前没有闪烁,并且最终状态与静态卡片匹配。
4. 录制 MP4 + GIF
在开发服务器运行时:
node <this skill folder>/scripts/record.mjs \
--url "http://localhost:<port>/og/<page>-animated?loop=0" \
--out out/og-image-animated/og-<page>
生成 og-<page>.mp4(2400×1260、H.264、30fps)、og-<page>.gif(1200px 宽、调色板优化、循环)和 og-<page>.png(最终帧)。工作原理:它会暂停页面上的每个动画,读取时间线长度,然后逐帧寻找所有动画并截图 [data-og-frame] 的固定区域。每次运行结果一致,无丢帧。选项:--fps、--hold、--lead、--scale、--formats mp4,gif,png、--selector。
需要 PATH 中有 ffmpeg。Playwright 会在首次运行时安装到此技能的 scripts/ 文件夹中,如果找不到其 Chromium 也会安装。
注意事项:
- 页面不得强制减少运动。许多全局样式表会缩短
prefers-reduced-motion下的动画;记录器不会模拟它,如果找不到动画会大声失败。 - 元素截图会等待“稳定”,这在动画中间永远不会发生。这就是脚本剪辑页面区域的原因。
- 确保端口确实正在提供此应用的响应(另一个本地服务器可以应答
localhost)。
5. 审查
提取联系表并查看:
ffmpeg -y -loglevel error -i og-<page>.mp4 \
-vf "select='eq(n\,8)+eq(n\,22)+eq(n\,40)+eq(n\,<last>)',scale=1000:-1,tile=2x2" -frames:v 1 sheet.png
- 约第 0.25 秒:空白卡片(仅发光),无布局弹出。
- 约第 0.7 秒:标题进入或接近进入,应用窗口尚未可见。
- 约第 1.3 秒:应用窗口升起中,标题完全可读。
- 最后一帧:内容与静态 PNG 一致。
保持 GIF 相对较小(约 3MB 以下)。如果更大,降低 --fps(20–24)或缩短 --hold。
输出
告知用户:
- 预览路由 URL(循环)以及写入的文件(MP4、GIF、PNG)
- 重新生成的命令
- 时间线(移动的元素、持续时间)
og:image必须保持为静态 PNG:社交平台不播放动画 OG 图像。在帖子、README、启动页面或支持的地方使用 MP4/GIF 作为og:video。