用大模型写代码做视频:7类技术路线与5个案例拆解
1. 为什么“用大模型写代码做视频”值得单独拎出来讲
前端圈子里有个挺有意思的现象:聊到用 AI 写代码,大家第一反应都是“帮我写个函数”“帮我改个 bug”“帮我生成一段正则”。但真正把大模型用出生产力的人,往往在做一件更狠的事——让模型直接产出可运行的视觉内容。视频、动画、交互式图形,这些过去需要设计师加前端加动效三个人配合的活儿,现在一个人加一个模型就能跑通。
我最近花了不少时间在 Claude Opus 这类强推理模型上折腾“写代码做视频”这件事,越做越觉得这里面有一套方法论。它不是简单地让模型吐一段canvas代码就完事,而是涉及技术路线选型、提示词框架设计、代码与渲染的衔接、性能与兼容性兜底这一整套工程问题。标题里提到的“7 类技术路线、5 个案例拆解、可复用提示词框架”,其实就是我踩了一圈坑之后总结出来的骨架。
这篇文章适合谁看?如果你是前端开发者,想把手里的JavaScript、Canvas、Three.js技能和 AI 结合起来做点视觉化的东西,那这篇就是给你写的。如果你是内容创作者,想用代码批量生成视频素材,也能从里面抄到不少可直接复用的提示词和代码结构。哪怕你只是刚接触canvas绘图,看完至少能明白“让模型写视频代码”这件事的边界在哪、坑在哪。
我尽量不写那种“首先……其次……最后”的教科书腔,就按我实际做项目的顺序来讲:先讲清楚有哪几条路可以走,再拆案例,最后把提示词框架摊开给你看。中间会穿插大量我实测下来的参数、报错和绕坑技巧,能直接抄的我都标出来了。
2. 七类技术路线全景拆解与选型逻辑
在动手之前,得先想明白一件事:“用代码做视频”到底有几种实现方式?这个问题不搞清楚,提示词都没法写,因为你不知道该让模型往哪个方向生成。我把实际能跑通的路线归成七类,每一类的适用场景、性能特征和提示词写法都不一样。
2.1 路线一:Canvas 2D 逐帧绘制
这是最基础也最可控的一条路。核心思路是用requestAnimationFrame驱动一个时间轴,每一帧根据当前时间t重新计算所有图形的位置、透明度、缩放,然后清屏重绘。视频的本质就是“一帧一帧的静态画面连续播放”,canvas绘图天然适合干这个。
它的优势是确定性极强。你给定t,画面就是唯一确定的,不会因为设备性能不同而出现画面差异。导出的时候只要按固定帧率把每一帧toDataURL出来,再用工具合成视频就行。缺点是复杂 3D 场景做不了,粒子数量一多就卡。
提示词上,你要明确告诉模型“用时间驱动、每帧重绘”这个模式,否则它很可能给你写一个静态绘制函数,动不起来。
2.2 路线二:Canvas + 离屏渲染批量导出
这条路是路线一的工程化版本。区别在于,它不追求实时播放,而是离线逐帧渲染。你设定总时长和帧率,比如 10 秒 30fps 就是 300 帧,循环 300 次,每次把t设为i/30,渲染完把 canvas 转成图片存下来。
为什么需要单独拎出来?因为实时播放会掉帧,而离线渲染可以保证每一帧都完整计算。我实测下来,一个中等复杂度的粒子动画,实时播放可能只有 40fps,但离线渲染能稳定输出 60fps 的序列帧。代价是渲染时间变长,300 帧可能要跑十几秒。
提示:离线渲染时记得把
requestAnimationFrame换成普通for循环,否则浏览器会按刷新率节流,导出速度反而更慢。
2.3 路线三:Three.js 实时 3D 场景
一旦涉及 3D 模型、光照、相机运动,Canvas 2D就不够用了,这时候上Three.js。它的核心是场景图(Scene Graph):你往场景里加相机、灯光、网格,渲染器每帧把场景投影到屏幕上。做视频时,你控制的是相机位置和物体变换随时间的变化。
three.js 快速创建项目其实不难,难的是让模型理解空间关系。我试过直接让模型“做一个旋转的立方体”,它能写出来;但让它“做一个相机绕着物体螺旋上升同时物体自转”这种复合运动,就得把运动拆成参数方程喂给它,否则它给的运动逻辑经常是错的。
2.4 路线四:Three.js + 着色器(Shader)程序化生成
这条路是进阶玩法。着色器跑在 GPU 上,可以做出流体、噪声、扭曲这类 CPU 算不动的效果。做视频时,你把时间uniform传进去,着色器里用sin(time)之类的函数驱动画面变化。
它的性能优势巨大,但提示词难度也最高。模型对 GLSL 的掌握程度参差不齐,经常写出编译不过的代码。我的经验是:让模型先写一个最小可用的着色器骨架,再逐步加效果,不要一上来就要一个复杂的噪声场。
2.5 路线五:DOM + CSS 动画录制
不是所有视频都需要 canvas。如果画面是文字、卡片、图表这类结构化内容,用 DOM 加 CSS 动画反而更简单。你可以用JavaScript控制元素的style,配合transition或animation,然后用html2canvas或者屏幕录制 API 抓帧。
这条路的好处是排版能力强,做数据可视化视频、文字动画特别合适。缺点是抓帧精度不如 canvas,CSS 动画的时间控制也没那么精确。
2.6 路线六:SVG 矢量动画
SVG 适合做线条、图标、路径类的动画。你可以用JavaScript动态改path的d属性,或者用stroke-dasharray做描边动画。导出时同样可以逐帧序列化。
它的优势是矢量无损,放大不糊,适合做 logo 动画、流程演示。但复杂场景下 DOM 节点太多会拖慢性能。
2.7 路线七:混合渲染(Canvas 打底 + DOM 叠加)
实际项目里,纯一条路线往往不够。我做过一个数据看板视频,背景是 canvas 粒子,前景是 DOM 文字和图表,两者用同一个时间轴驱动。这种混合方案灵活,但要注意层级和时间同步,否则会出现背景和前景对不上的尴尬。
下面这张表是我对这七条路线的横向对比,选型的时候可以直接对照:
| 路线 | 适用场景 | 性能 | 提示词难度 | 导出精度 |
|---|---|---|---|---|
| Canvas 2D 逐帧 | 2D 动画、粒子 | 中 | 低 | 高 |
| Canvas 离屏渲染 | 需要稳定帧率 | 中 | 低 | 极高 |
| Three.js 实时 3D | 3D 场景、模型 | 中高 | 中 | 高 |
| Three.js + Shader | 程序化特效 | 高 | 高 | 高 |
| DOM + CSS | 文字、图表 | 高 | 低 | 中 |
| SVG 矢量 | 线条、图标 | 中 | 中 | 高 |
| 混合渲染 | 复杂综合场景 | 中 | 高 | 中高 |
选型的核心逻辑就一句话:先看画面是 2D 还是 3D,再看是结构化内容还是自由绘制,最后看性能要求。想清楚这三点,路线基本就定了。
3. 五个案例拆解:从提示词到可运行代码
光讲路线太虚,直接上案例。这五个案例是我从简单到复杂排的,每个都附上我实际用的提示词思路和关键代码结构。你可以按顺序看,也可以挑感兴趣的跳。
3.1 案例一:Canvas 粒子文字动画
需求很简单:一堆粒子从随机位置汇聚成一个文字,然后散开。这是最经典的 canvas 动画之一,也是检验模型能不能理解“时间驱动”的好例子。
我给模型的提示词核心是这几句:“用 Canvas 2D 实现粒子汇聚成文字的效果。先在一个离屏 canvas 上绘制文字,读取像素数据,把有像素的位置作为粒子的目标点。粒子初始位置随机,随时间从初始位置插值到目标位置。用 requestAnimationFrame 驱动,传入时间参数 t。”
关键代码结构是这样的:
const offCanvas = document.createElement('canvas'); const offCtx = offCanvas.getContext('2d'); offCtx.font = 'bold 120px sans-serif'; offCtx.fillText('HELLO', 0, 120); const imageData = offCtx.getImageData(0, 0, offCanvas.width, offCanvas.height); const targets = []; for (let y = 0; y < imageData.height; y += 4) { for (let x = 0; x < imageData.width; x += 4) { const index = (y * imageData.width + x) * 4; if (imageData.data[index + 3] > 128) { targets.push({ x, y }); } } }这里有个细节:采样步长+= 4决定了粒子密度。步长越小粒子越多,画面越清晰但越卡。我实测 120px 的文字用步长 4 大概能得到两三千个粒子,在中端笔记本上能跑满 60fps。如果步长改成 2,粒子数翻四倍,直接掉到 20fps。
注意:
getImageData读取像素时,如果 canvas 被跨域图片污染过会报安全错误。纯文字绘制没这个问题,但如果你要基于图片生成粒子,记得图片要么同源要么走crossOrigin处理。
模型第一次给我的代码里,粒子是瞬间跳到目标位置的,没有过渡。我在提示词里补了一句“用缓动函数控制插值进度,让粒子先慢后快再慢”,它才加上easeInOutCubic。这就是提示词要迭代的地方,别指望一次到位。
3.2 案例二:Three.js 旋转立方体与相机运动
这个案例的目标是做一个 3D 立方体,相机绕着它转,同时立方体自身也在旋转。听起来简单,但涉及两个独立运动叠加,模型很容易搞混。
我的提示词是:“用 Three.js 创建一个场景,包含一个带纹理的立方体。相机沿一条螺旋线运动,同时始终看向立方体中心。立方体自身绕 Y 轴匀速旋转。用 Clock 获取时间,在动画循环里更新相机位置和立方体旋转。”
相机螺旋线的参数方程我是直接给模型的:
const t = clock.getElapsedTime(); const radius = 5; const height = 3; camera.position.x = radius * Math.cos(t * 0.5); camera.position.z = radius * Math.sin(t * 0.5); camera.position.y = height * Math.sin(t * 0.3); camera.lookAt(0, 0, 0);为什么要给参数方程?因为如果只说“相机绕着转”,模型可能给你一个简单的圆周运动,没有高度变化,画面就很平。给了方程,运动轨迹就完全可控了。
three.js 快速创建项目的模板代码其实很固定:场景、相机、渲染器三件套,加上requestAnimationFrame循环。我一般会让模型先输出这个骨架,确认能跑起来,再往里加内容。这样出问题的时候好定位,不会一上来就是一堆代码报错。
3.3 案例三:Shader 流体噪声背景
这个案例难度上来了。目标是做一个流动的噪声背景,类似烟雾或者水波的效果。核心是用 GLSL 写一个片段着色器,用time驱动噪声场变化。
提示词我是这么写的:“写一个 Three.js 全屏平面,用 ShaderMaterial 渲染。片段着色器里实现一个 2D 噪声函数,用 time uniform 驱动噪声的偏移,产生流动效果。颜色从深蓝渐变到紫色。”
模型给的噪声函数是经典的 value noise,我贴一下核心部分:
float noise(vec2 p) { vec2 i = floor(p); vec2 f = fract(p); f = f * f * (3.0 - 2.0 * f); float a = hash(i); float b = hash(i + vec2(1.0, 0.0)); float c = hash(i + vec2(0.0, 1.0)); float d = hash(i + vec2(1.0, 1.0)); return mix(mix(a, b, f.x), mix(c, d, f.x), f.y); }这里踩了个坑:模型第一次写的hash函数在某些 GPU 上精度不够,画面会出现明显的块状伪影。我换成基于sin的 hash 之后才正常。所以着色器代码一定要在目标设备上实测,不能只看逻辑对不对。
提示:GLSL 里
sin函数在大数值下精度会下降,hash 函数里最好用fract(sin(dot(...)) * 43758.5453)这种经典写法,兼容性最好。
3.4 案例四:数据可视化动态图表
这个案例偏实用。需求是把一组数据做成动态柱状图,柱子从 0 长到目标高度,同时数字滚动增长。这种视频在汇报、宣传里特别常见。
我用的是 Canvas 2D 路线,因为要精确控制每一帧。提示词里我强调了“用缓动函数控制柱子高度和数字的插值,两者必须同步”。模型给的方案是用同一个进度值p驱动两个属性:
const p = easeOutCubic(Math.min(t / duration, 1)); const barHeight = targetHeight * p; const displayValue = Math.floor(targetValue * p);同步这件事很关键。我见过不少实现是柱子动画和数字动画各跑各的,结果柱子长完了数字还在跳,看着就很业余。用同一个进度值驱动,就绝对不会不同步。
另外,javascript保留两位小数这种需求在这里也会遇到。数字滚动时如果直接显示原始值,会出现一长串小数。用toFixed(2)处理一下,画面干净很多。但注意toFixed返回的是字符串,如果后续还要参与计算,记得用parseFloat转回来。
3.5 案例五:混合渲染的片头动画
最后一个案例综合度最高:背景是 canvas 粒子,中间是 SVG 描边动画的 logo,底部是 DOM 文字淡入。三条路线混在一起,用同一个时间轴驱动。
这种项目的提示词最难写,因为要描述清楚层级关系和时间编排。我的做法是分模块描述:先让模型写背景粒子,再写 SVG 描边,最后写 DOM 文字,每个模块都接收一个统一的t参数。最后用一个主循环把t传给三个模块。
function render(t) { renderParticles(t); renderSvgStroke(t); renderText(t); requestAnimationFrame(() => render(performance.now() / 1000)); }这里有个坑:performance.now()返回的是毫秒,而且起点是页面加载时间,不是 0。所以要么除以 1000 转成秒,要么记录一个起始时间做差值。我一般用后者,更精确:
const startTime = performance.now(); function render() { const t = (performance.now() - startTime) / 1000; // ... }混合渲染的另一个问题是层级和混合模式。canvas 默认是不透明的,会盖住下面的 DOM。要么把 canvas 设成position: absolute加z-index控制层级,要么用globalCompositeOperation做混合。我一般用前者,简单直接。
4. 可复用提示词框架:让模型稳定产出可运行代码
讲了这么多案例,你会发现一个规律:提示词的质量直接决定代码能不能跑。我总结了一套框架,分五层,从角色设定到输出约束,每一层都有明确作用。
4.1 第一层:角色与技术栈锚定
开头一定要把模型的身份和技术栈钉死。比如:“你是一名资深前端图形工程师,精通 JavaScript、Canvas 2D、Three.js 和 GLSL。接下来所有代码都用原生 JavaScript 编写,不依赖任何构建工具,能直接在浏览器里运行。”
为什么要强调“不依赖构建工具”?因为模型默认可能给你 React 组件或者需要 npm 安装的代码,而做视频这种场景,往往就是一个 HTML 文件加一段 script,越简单越好。锚定技术栈能省掉大量来回修改。
4.2 第二层:渲染模式与时间驱动声明
这一层是核心。必须明确告诉模型用哪种渲染模式,以及时间怎么驱动。我常用的句式是:“使用 requestAnimationFrame 驱动动画,每一帧根据当前时间 t 重新计算所有图形状态并重绘。t 从 0 开始,单位为秒。”
如果是离线渲染,就改成:“使用 for 循环逐帧渲染,总帧数 N,帧率 fps,第 i 帧的时间 t = i / fps。”
这句话看着简单,但它决定了整个代码的架构。没有这句话,模型很可能写一个静态绘制函数,你还得自己改成动画。
4.3 第三层:画面元素与运动描述
这一层描述具体画什么、怎么动。我的经验是用参数方程描述运动,比用自然语言描述准确得多。比如不说“相机绕着物体转”,而说“相机位置为 (Rcos(wt), hsin(w2t), Rsin(wt)),始终 lookAt 原点”。
颜色、尺寸、数量这些参数也尽量给具体值,不要用“一些”“适当”这种模糊词。模型对具体数字的执行准确率远高于模糊描述。
4.4 第四层:性能与兼容性约束
这一层很多人会忽略,但实际项目里特别重要。我会加上:“注意性能,粒子数量控制在 5000 以内。避免使用实验性 API。所有代码需兼容主流浏览器。”
如果涉及canvas绘图的像素操作,还要提醒:“getImageData 采样步长不小于 2,避免性能问题。”这些约束能帮模型避开很多常见的性能陷阱。
4.5 第五层:输出格式与自检要求
最后一层约束输出格式:“先输出完整的 HTML 文件,包含所有代码。代码中关键参数用注释说明。输出后自查:是否有未定义的变量、是否有语法错误、动画是否能循环播放。”
让模型自检这一步很关键。我实测下来,加了自检要求之后,代码一次跑通的概率能从六成提到八成以上。模型在自检时会自己发现一些低级错误,比如变量名拼错、函数没定义。
把这五层拼起来,就是一个完整的提示词模板。我一般会把它存成一个片段,每次改改中间的画面描述就能复用。
5. 实操中的高频问题与排查技巧
代码跑不起来是常态,关键是怎么快速定位。我把这段时间遇到的高频问题整理成一张速查表,附上排查思路。
| 问题现象 | 可能原因 | 排查方法 |
|---|---|---|
| 画面全黑 | 渲染循环没启动 / 相机位置不对 | 检查 requestAnimationFrame 是否调用,相机是否在物体附近 |
| 动画卡顿 | 粒子过多 / 每帧创建对象 | 减少粒子数,把对象创建移到循环外 |
| 画面闪烁 | 没清屏 / 清屏不完整 | 每帧开头调用 clearRect 或 fillRect 覆盖 |
| 着色器编译失败 | GLSL 语法错误 | 看控制台报错行号,检查分号和类型 |
| 导出视频花屏 | 帧率不匹配 / 编码问题 | 确认序列帧帧率与合成帧率一致 |
| 数字显示异常 | 浮点精度问题 | 用 toFixed 处理显示,计算时保留原始值 |
javascript运行时报错里最常见的就是undefined is not a function和Cannot read property of null。前者一般是方法名写错或者对象没初始化,后者一般是 DOM 元素还没加载就取。我的习惯是把所有 DOM 操作放在DOMContentLoaded之后,或者把 script 标签放 body 末尾。
还有一个坑是oc和javascript互相调用这种跨语言场景。如果你在做原生应用里嵌 WebView 做视频,JS 和原生之间的通信要特别注意时序。JS 调用原生方法往往是异步的,不能假设调用完就立刻有结果。我一般用回调或者 Promise 包一层,避免时序问题。
提示:调试 canvas 动画时,可以先把帧率降下来,比如用
setTimeout每 500ms 渲染一帧,这样能一帧一帧看清楚画面变化,比盯着快速动画找问题容易得多。
6. 我踩过的几个印象深刻的坑
说几个具体的。有一次做粒子动画,模型给的代码里粒子颜色是随机生成的,每次刷新颜色都不一样。这在实时播放时没问题,但导出视频时,每一帧的颜色都不同,合成出来就是一片闪烁的噪点。后来我把颜色改成基于粒子索引的确定性函数,问题才解决。凡是涉及导出的项目,所有随机都必须可复现,这是铁律。
还有一次用 Three.js 做场景,模型用了MeshStandardMaterial但没加灯光,结果物体全黑。我一开始以为是相机问题,调了半天才发现是缺灯光。标准材质需要光源,基础材质不需要,这个区别模型有时候会搞混。后来我在提示词里明确写了“使用 MeshBasicMaterial 或确保添加了环境光和方向光”,就没再出过这个问题。
最后一个坑是关于图片转canvas的。我想把一张 logo 图片转成粒子,用drawImage画到 canvas 上再读像素。结果因为图片是异步加载的,drawImage执行时图片还没加载完,画上去是空的。解决办法是把后续逻辑放在img.onload回调里。这个坑很经典,但每次换项目还是容易忘。
这些坑说到底都是时序和确定性的问题。做视频和做交互页面最大的区别就是:交互页面可以容忍一定的随机和异步,但视频要求每一帧都精确可控。想明白这一点,很多问题就迎刃而解了。
7. 后续可以怎么扩展
这套东西跑通之后,能玩的花样其实很多。比如把渲染好的序列帧接到 FFmpeg 上自动合成 MP4,整个流程就能一键出片。再比如把提示词框架做成一个模板系统,输入画面描述自动生成代码,那就是一个简易的“代码视频生成器”了。
我现在做新项目基本就是三步:选路线、套提示词框架、跑通后调参数。大部分时间花在调参数上,因为画面效果这东西,模型给的是及格线,要出彩还得自己磨。但至少从零到能跑,这个过程的效率比手写高了不止一个量级。
如果你也在折腾类似的东西,建议先从 Canvas 2D 那条路线入手,把时间驱动和逐帧渲染的逻辑吃透,再往 Three.js 和 Shader 上走。基础打牢了,后面换什么技术栈都是通的。