Text Rain Generator
将诗句/文字转换成艺术化的"文字雨"动效,参考景桐的 Vibe Coding 风格。
核心能力
- 智能生图:通过
/andy-relay调度 ChatGPT 生成黑白老照片风格的背景图 - 物理引擎:p5.js 粒子系统模拟真实雨滴效果(重力、风、碰撞)
- 参数化控制:可调节雨速、倾斜、颜色、布局等
- 即开即用:输出完整 HTML 文件,浏览器直接运行
使用方式
基础模式(最快)
/text-rain-generator "床前明月光,疑是地上霜" --scene "江南老宅屋檐"
高级模式(精细调参)
/text-rain-generator "山重水复疑无路,柳暗花明又一村" \
--scene "苏州园林青瓦屋檐,斑驳墙壁" \
--speed 3 \
--tilt 15 \
--color "#e8d5b7" \
--no-preview
参数说明
| 参数 | 默认值 | 说明 |
|---|---|---|
--scene |
"中式老宅屋檐" | 背景场景描述,传递给 ChatGPT 生图 |
--speed |
2.5 | 雨滴下落速度(1-5) |
--tilt |
10 | 最大倾斜角度(度) |
--color |
"#d4c5a9" | 文字颜色(十六进制) |
--font-size |
24 | 字体大小(px) |
--canvas-width |
1080 | 画布宽度 |
--canvas-height |
1920 | 画布高度(默认竖屏) |
--no-preview |
false | 不自动打开浏览器预览 |
--output |
./text-rain-output/ |
输出目录 |
工作流程
第1步:解析输入
- 拆解诗句为字符数组
- 验证参数合法性
- 生成项目文件夹结构
第2步:生成背景图(通过 andy-relay)
调用 /andy-relay 发送任务到 ChatGPT:
{
"objective": "Generate background image for text rain animation",
"deliverable": "One PNG image, 1080x1920, black and white old photo style",
"context": {
"scene": "{用户提供的场景描述}",
"style": "monochrome, vintage, film grain, high contrast",
"composition": "bottom 2/3 shows roof tiles/eaves, top 1/3 empty for rain"
},
"prompt": "Generate a black and white vintage photograph: {scene}. Composition: traditional Chinese roof tiles occupy bottom 60%, weathered texture with visible gaps between tiles. Top 40% is empty sky/wall for text overlay. High contrast, film grain, 1980s photo quality. Vertical format 1080x1920.",
"acceptance": [
"Image resolution exactly 1080x1920",
"Monochrome (no color)",
"Clear roof/eaves structure in bottom portion",
"Sufficient empty space at top"
]
}
重要:
- 使用 ChatGPT 的 DALL-E 3 生图能力
- 下载生成的图片到项目
assets/目录 - 验证图片尺寸和风格
第3步:生成 p5.js 代码
创建核心文件:
index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Text Rain - {首句诗}</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.7.0/p5.min.js"></script>
<script src="sketch.js"></script>
<style>
body {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: #1a1a1a;
}
canvas {
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
}
</style>
</head>
<body></body>
</html>
sketch.js
核心逻辑:
// 全局变量
let particles = [];
let backgroundImg;
let poem = []; // 诗句字符数组
let config = {
speed: {SPEED},
tilt: {TILT},
color: '{COLOR}',
fontSize: {FONT_SIZE},
canvasWidth: {CANVAS_WIDTH},
canvasHeight: {CANVAS_HEIGHT}
};
// p5.js 生命周期
function preload() {
backgroundImg = loadImage('assets/background.png');
}
function setup() {
createCanvas(config.canvasWidth, config.canvasHeight);
textFont('STKaiti'); // 楷体,适合中文诗词
textSize(config.fontSize);
textAlign(CENTER, CENTER);
// 初始化粒子
poem = '{POEM_TEXT}'.split('');
for (let i = 0; i < poem.length * 3; i++) {
particles.push(new RainDrop(random(poem)));
}
}
function draw() {
// 渲染背景
image(backgroundImg, 0, 0, width, height);
// 更新和渲染粒子
for (let p of particles) {
p.update();
p.display();
// 循环利用粒子
if (p.y > height) {
p.reset();
}
}
}
// 雨滴类
class RainDrop {
constructor(char) {
this.char = char;
this.reset();
}
reset() {
this.x = random(width * 0.1, width * 0.9); // 左右留白
this.y = random(-500, -50);
this.speed = random(config.speed * 0.8, config.speed * 1.2);
this.rotation = random(-config.tilt, config.tilt);
this.wobble = random(-0.5, 0.5); // 左右摆动
this.alpha = random(150, 255);
}
update() {
// 重力加速
this.y += this.speed;
this.x += sin(this.y * 0.01) * this.wobble;
// 碰撞检测:屋檐区域(底部 60%)
if (this.y > height * 0.4) {
let imgColor = backgroundImg.get(this.x, this.y);
let brightness = (imgColor[0] + imgColor[1] + imgColor[2]) / 3;
// 碰到深色区域(瓦片)改变轨迹
if (brightness < 100) {
this.speed *= 0.95; // 减速
this.x += random(-2, 2); // 随机溅开
this.rotation += random(-5, 5);
}
}
}
display() {
push();
translate(this.x, this.y);
rotate(radians(this.rotation));
fill(config.color + hex(this.alpha, 2));
noStroke();
text(this.char, 0, 0);
pop();
}
}
第4步:输出和验证
生成文件结构:
text-rain-output/
├── index.html
├── sketch.js
├── assets/
│ └── background.png
├── README.md
└── preview.sh # 快速启动脚本
README.md
# 文字雨动效 - {诗句首句}
## 快速运行
### 方法1:直接打开
双击 `index.html` 在浏览器中打开
### 方法2:本地服务器(推荐)
```bash
# Python 3
python -m http.server 8000
# 或使用 Node.js
npx serve .
录屏导出
macOS
⌘ + Shift + 5 选择录制区域
Windows
Win + G 打开 Xbox Game Bar
通用方案
使用 OBS Studio 录制浏览器窗口
参数调整
编辑 sketch.js 中的 config 对象:
speed: 雨速(1-5)tilt: 倾斜角度(0-30)color: 文字颜色fontSize: 字号
技术栈
- p5.js 1.7.0
- 纯前端,无需构建
#### `preview.sh`
```bash
#!/bin/bash
# 自动启动本地服务器并打开浏览器
echo "🌧️ 启动文字雨预览..."
# 检测可用工具
if command -v python3 &> /dev/null; then
echo "使用 Python 服务器 (http://localhost:8000)"
python3 -m http.server 8000 &
SERVER_PID=$!
sleep 2
open http://localhost:8000
elif command -v npx &> /dev/null; then
echo "使用 Node.js serve"
npx serve . -l 8000 &
SERVER_PID=$!
sleep 2
open http://localhost:8000
else
echo "⚠️ 未找到 Python 或 Node.js,请手动打开 index.html"
open index.html
exit 0
fi
echo "✅ 预览已启动!按 Ctrl+C 停止服务器"
trap "kill $SERVER_PID" EXIT
wait $SERVER_PID
验证清单
自动执行以下检查:
- 背景图已下载且尺寸正确(1080x1920)
- 诗句文字已正确拆分
-
sketch.js语法无错误 - 浏览器控制台无报错
- 粒子系统正常运行(至少100帧无卡顿)
- 碰撞效果触发正常
如果 --no-preview 未设置,自动通过 WebBridge 打开预览。
故障排查
图片生成失败
- 原因:ChatGPT 限流/登录过期
- 解决:手动上传 1080x1920 黑白屋檐图到
assets/background.png
中文乱码
- 原因:字体未加载
- 解决:在
setup()中指定系统字体或使用 Web Font
性能卡顿
- 原因:粒子数过多
- 解决:减少
particles初始化数量(默认poem.length * 3)
进阶扩展
添加音效
let rainSound;
function preload() {
backgroundImg = loadImage('assets/background.png');
rainSound = loadSound('assets/rain.mp3');
}
function setup() {
// ...
rainSound.loop();
}
导出视频(Node.js)
使用 p5.js + canvas 库逐帧导出:
npm install canvas p5
node export-frames.js # 生成帧序列
ffmpeg -i frame_%04d.png -c:v libx264 output.mp4
Claude Code 执行流程
当用户调用 /text-rain-generator 时,按以下步骤执行:
Step 1: 解析参数并生成项目
cd ~/.claude/skills/text-rain-generator
node cli.ts "{用户提供的诗句}" {其他参数}
CLI 会输出 JSON 格式的结果,包含:
outputDir: 项目输出目录taskPacketPath: ChatGPT 任务包路径backgroundPath: 背景图目标路径indexPath: HTML 入口文件路径config: 完整配置对象
Step 2: 调用 andy-relay 生成背景图
读取 taskPacketPath 中的任务包,通过 /andy-relay 调度 ChatGPT:
// 伪代码示例
const taskPacket = JSON.parse(fs.readFileSync(taskPacketPath));
// 调用 andy-relay
await invokeSkill('andy-relay', {
provider: 'chatgpt',
task: taskPacket,
downloadTo: backgroundPath,
});
重要:
- 使用 WebBridge 工具与 ChatGPT 交互
- 等待图片生成完成(可能需要 10-30 秒)
- 从 ChatGPT 界面下载图片到
backgroundPath - 验证图片尺寸和格式
Step 3: 验证背景图
# 检查文件是否存在
ls -lh {backgroundPath}
# 验证图片尺寸(需要 ImageMagick 或用 Node.js)
identify {backgroundPath} # 或用 sharp 库
确认:
- 文件存在且大小 > 100KB
- 尺寸符合配置(默认 1080×1920)
- 格式为 PNG 或 JPG
Step 4: 启动预览(如果未设置 --no-preview)
cd {outputDir}
./preview.sh
或通过 WebBridge 打开:
// 使用 mcp__webbridge__navigate
await webbridge.navigate({
url: 'http://localhost:8000/index.html',
session: 'text-rain-preview',
});
// 等待 2 秒让页面加载
await sleep(2000);
// 截图验证
await webbridge.screenshot({
session: 'text-rain-preview',
path: `{outputDir}/preview.png`,
});
Step 5: 报告结果
向用户报告:
- ✅ 项目已生成到:
{outputDir} - ✅ 背景图已生成:
{backgroundPath} - ✅ 预览已启动:
http://localhost:8000 - 📝 使用说明: 查看
{outputDir}/README.md
如果某步失败,提供清晰的错误信息和解决方案。
错误处理
ChatGPT 生图失败
- 症状: 任务包已发送,但 ChatGPT 返回错误或超时
- 解决:
- 检查 ChatGPT 账号是否有 DALL-E 3 权限
- 简化场景描述(去掉复杂细节)
- 让用户手动上传背景图到
assets/background.png
图片尺寸不匹配
- 症状: 下载的图片尺寸与配置不符
- 解决: 使用 ImageMagick 或 sharp 库调整尺寸
convert {backgroundPath} -resize 1080x1920! {backgroundPath}
WebBridge 连接失败
- 症状:
mcp__webbridge__*工具报错 - 解决:
- 确认 Kimi WebBridge 服务运行中
- 运行
~/.kimi-webbridge/bin/kimi-webbridge start - 如果仍失败,降级为手动打开浏览器
预览页面空白
- 症状: 浏览器打开但只看到黑屏
- 解决:
- 打开浏览器控制台查看错误
- 检查
assets/background.png是否存在 - 验证
sketch.js语法无错误 - 确认本地服务器正常运行(非直接打开 HTML)
灵感来源
- 景桐 Vibe Coding 文字雨案例
- p5.js 官方粒子系统示例
- 日本枯山水美学 + 中式诗词意境