graphic-gif
从CSS动画或AI图像转视频API生成循环播放的动画GIF。输出:animation.gif。
与其他输出静态PNG或PDF的graphic-技能不同,此技能输出动画的.gif文件。它使用CSS @keyframes 动画,通过Playwright和Web Animations API逐帧捕获(选项A,默认),或通过Kling实现AI图像转视频管道(选项B)。
关键规则(每次生成前必读)
- 默认使用css-animated。 除非明确要求,否则不要使用
ai-generated。 - 画布为800×800像素正方形。 所有
clamp()值按800px计算(1vw = 8px)。 - 单个自包含HTML。 所有CSS内联在
<style>中。字体CDN<link>是唯一的外部依赖。 - 切勿在聊天中直接转储HTML。 保存到文件,仅显示摘要。
- 帧捕获使用Web Animations API的seeking。 不要使用
setTimeout循环,也不要使用animation-delay技巧。 - 精确帧数:
Math.floor(duration_seconds * fps)帧。不得捕获t=duration_ms时刻的帧——它会与t=0重复,导致循环点处出现可见卡顿。 - 不得使用占位框。 仅使用CSS生成的可视内容。不要出现“此处放置图像”之类的元素。
- 更简单的调色板 = 更小的文件。 使用:
clean-slate、terminal、electric-burst、brutalist。 - 不要使用animation-delay实现交错。 将交错直接编入
@keyframes百分比——帧seeking会处理时序。 - 在编写CSS之前确定设计方向。 基调、标志性元素、运动风格、令人难忘的细节——在编写第一行代码之前全部确定。
步骤1:输入
必填: prompt(内容描述和动态简述)
可选参数及默认值:
| 参数 | 默认值 | 选项 |
|---|---|---|
| animation_type | css-animated | css-animated / ai-generated |
| duration | 3.0 | 秒 |
| fps | 12 | 帧/秒 |
| loop | true | true / false |
| style | clean-slate | clean-slate / terminal / electric-burst / brutalist |
| dimensions | 800x800 | 像素(宽x高) |
| optimization | balanced | quality / balanced / filesize |
如果缺少提示或没有动态描述,请准确提问:
“GIF应该显示什么内容?请描述内容以及动态效果(例如:'统计数据逐个递增:73%的购买者在购买前阅读3篇以上内容。打字机效果,一次一个字符。样式:terminal。3秒,12fps。')
关键设置(均为可选,显示默认值):
- animation_type: css-animated(默认)或 ai-generated
- duration: 3.0 秒
- fps: 12
- loop: true
- style: clean-slate(选项:clean-slate / terminal / electric-burst / brutalist)
- dimensions: 800x800
- optimization: balanced(选项:quality / balanced / filesize)”
如果所有必要信息均已提供 → 直接跳至步骤2。
步骤2:内部架构(绝不向用户展示)
对于css-animated:
- 从以下动画类型中选择:
fade-in、slide-in、typewriter、counter、pulse、loop-scroll - 阅读
references/animation-library.md— 找到所选类型的完整HTML/CSS规范 - 阅读
references/style-presets.md— 加载所选样式的CSS令牌块 - 计算帧数:
Math.floor(duration_seconds * fps)— 记下这个数字 - 确定设计方向:
| 决策 | 推导依据 |
|---|---|
| 基调 | 面向受众的情感基调(机械/温暖/电气/专业) |
| 标志性元素 | 一致使用一种视觉装置(光标闪烁、幽灵数字、扫描线叠加、强调边框) |
| 运动风格 | 此类动画的缓动曲线理念(弹簧/线性/步进/缓入缓出) |
| 令人难忘的细节 | 观看者记住此GIF的“唯一”要素 |
对于ai-generated:
- 生成基础静态帧HTML(海报风格布局,适合画布)
- 使用截图导出为PNG
- 调用Kling API:
POST https://api.klingai.com/v1/videos/image2video,参数为image_url和描述动态的prompt - 轮询任务完成状态
- 下载视频 → 使用ffmpeg转换为GIF:
# 两次调色板以获得最佳色彩质量 ffmpeg -i input.mp4 -vf "fps=12,scale=800:800:flags=lanczos,palettegen=stats_mode=diff" palette.png ffmpeg -i input.mp4 -i palette.png -vf "fps=12,scale=800:800:flags=lanczos,paletteuse=dither=bayer:bayer_scale=5" output.gif
步骤3:HTML生成(css-animated路径)
在生成之前,先阅读 references/animation-library.md 和 references/style-presets.md。
画布基础 — 每个GIF必需的CSS:
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
body {
width: 800px;
height: 800px;
overflow: hidden;
background: var(--bg);
font-family: var(--font-body);
}
.canvas {
width: 800px;
height: 800px;
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
动画规则:
- 所有动画元素使用
animation-fill-mode: forwards(或both) - 每种类型的缓动函数:
typewriter→steps(N, end),其中N = 精确的字符数counter→linearfade-in→cubic-bezier(0.22, 1, 0.36, 1)(缓出)slide-in→cubic-bezier(0.34, 1.56, 0.64, 1)(弹簧过冲)pulse→ease-in-out,同时animation-iteration-count: infiniteloop-scroll→linear,同时animation-iteration-count: infinite
- 对于一次性动画(fade-in、slide-in、typewriter、counter):
animation-iteration-count: 1— 循环在GIF层面处理 - 不要使用
animation-delay— 交错直接编入@keyframes百分比
打字机效果N的计算: 统计每个字符,包括空格、标点、数字:
- "73% of buyers" = 14个字符 →
steps(14, end) - "Hello, World!" = 13个字符 →
steps(13, end)
计数器CSS @property(counter类型必需):
@property --num {
syntax: '<integer>';
inherits: false;
initial-value: 0;
}
.counter {
animation: countUp var(--duration) linear forwards;
counter-reset: num var(--num);
}
.counter::after { content: counter(num); }
@keyframes countUp {
from { --num: 0; }
to { --num: var(--target); }
}
循环滚动:内容必须复制:
<!-- 4个原始项 + 4个重复项 -->
<div class="ticker-track" style="--item-count: 4;">
[item1][item2][item3][item4][item1][item2][item3][item4]
</div>
使用 translateX(0 → -50%) 配合 linear infinite。
设计质量规则(来自步骤2的承诺):
- 步骤2中命名的标志性元素必须在CSS/HTML中出现(不仅仅是描述)
- 排版:显示文本与辅助文本之间的字重对比至少为2:1(例如700比400)
- 背景:深色样式不要使用纯白
#fff— 使用预设中准确的--bg值 - terminal样式:添加扫描线叠加层
::after,使用repeating-linear-gradient,opacity: 0.03 - brutalist样式:关键元素使用粗边框
4px solid #000或4px solid var(--accent) - 令人难忘的细节:如果需要额外元素来实现 — 立即添加
步骤4:自我QA(在步骤5之前修复所有失败)
画布:
-
body和.canvas恰好为800×800px(或指定尺寸) -
body和.canvas都设置了overflow: hidden - 没有元素超出画布边界
动画:
- 任何地方都没有使用
animation-delay— 交错在@keyframes百分比中处理 - 所有动画从
t=0开始(Web Animations API将从那里开始seeking) - 所有动画元素设置了
animation-fill-mode: forwards或both - 一次性动画:
animation-iteration-count: 1 - 无限动画(pulse、loop-scroll):
animation-iteration-count: infinite
类型特定检查:
- 打字机效果:
steps(N, end)中的N = 文本字符串的精确字符数 - 计数器:使用
syntax: '<integer>'和initial-value: 0声明了@property --num - 计数器:使用了
counter-reset: num var(--num)以及::after { content: counter(num) } - 循环滚动:HTML中内容恰好复制了一次
设计:
- 没有占位框
- 从
references/style-presets.md应用了样式预设令牌 — 没有自由散落的十六进制颜色 - 步骤2中命名的标志性元素实际出现在HTML/CSS中
- 步骤2中的令人难忘的细节已实际实现
- 所选样式对应的字体CDN
<link>已存在
步骤5:导出
根据提示确定slug(kebab-case,不超过30个字符)。创建输出目录:
mkdir -p [slug]
保存HTML:
[slug]/animation.html
在浏览器中打开以快速视觉检查:
open [slug]/animation.html
运行导出脚本(将 [skill-root] 替换为此技能的实际路径):
bash [skill-root]/scripts/export-gif.sh \
[slug]/animation.html \
[slug]/animation.gif \
--duration [duration] \
--fps [fps] \
[--no-loop 如果loop=false] \
--optimization [optimization] \
--width [W] \
--height [H]
该脚本会:
- 在临时目录中安装
gifenc、sharp(或jimp)和playwright - 下载Chromium(如未缓存)
- 运行
capture-and-encode.mjs— 暂停动画、seeking每一帧、截图、组装GIF - 如果可用,运行
gifsicle优化通道 - 报告文件大小并打开结果
如果导出脚本在 [skill-root]/scripts/export-gif.sh 未找到,请检查技能是否已随其 scripts/ 文件夹一起完整安装。
步骤6:输出摘要
成功导出后显示:
## GIF:[1行描述]
日期:[YYYY-MM-DD] | 样式:[style] | 动画:[type] | [duration]秒 @ [fps]fps
尺寸:[WxH] | 帧数:[N] | 循环:[true/false]
文件
源文件: [slug]/animation.html
输出: [slug]/animation.gif
大小: [X] KB
检查清单
- [ ] 预览时首尾循环流畅(无卡顿)
- [ ] 在目标显示尺寸下文本清晰可读
- [ ] 文件大小合适:邮件 <500KB / 社交媒体 <3MB
AI生成路径(选项B)
仅当明确指定 animation_type: ai-generated 时使用。
要求:
- 环境中设置Kling API密钥:
KLING_API_KEY(每天66次免费额度,免费层无需信用卡) - 本地安装
ffmpeg用于视频→GIF转换
工作流程:
- 生成与提示匹配的基础静态帧HTML(指定尺寸的海报/图形风格)
- 将静态帧导出为PNG:
# 通过Playwright快速截图 node -e " const { chromium } = require('playwright'); (async () => { const browser = await chromium.launch(); const page = await browser.newPage({ viewport: { width: W, height: H } }); await page.goto('file://[slug]/animation.html'); await page.screenshot({ path: '[slug]/base-frame.png' }); await browser.close(); })(); " - 上传到Kling图像转视频端点
- 使用ffmpeg两次调色板将结果转换为GIF
- 应用gifsicle优化
当Kling不可用时: 回退到css-animated,并向用户附注说明:“AI生成需要Kling API密钥(KLING_API_KEY)。回退到css-animated。设置密钥以启用AI生成。”
提示技巧(用户请求指导时显示)
“描述动态,而不仅仅是内容。'统计数据一个接一个递增'优于'显示统计数据'。”
“为控制文件大小,保持简洁。1-3个动画元素配合纯色背景。”
“以循环为思路。动画应该从结束到开始流畅地无缝过渡。”
“明确指定动画类型。
typewriter和counter在社交媒体上最有效。”✅ 好的示例:“创建一个动画GIF,css-animated,打字机效果。文本:'73%的B2B购买者在联系销售之前阅读3篇以上内容。'每个字符逐个打出。样式:terminal。3秒,12fps,loop=true。”
❌ 不好的示例:“制作一个营销技巧的动画GIF”