1. 为什么“写代码做视频”正在成为一条新路线过去两年视频内容的生产方式悄悄发生了分层。最上面一层是传统剪辑软件Premiere、Final Cut、达芬奇靠时间线、关键帧、素材箱吃饭中间一层是模板化工具剪映、CapCut、Canva拖拖拽拽就能出片最下面一层是最近一年冒出来的新玩法——用代码直接生成视频。不是用代码去调用剪辑软件的接口而是把视频的每一帧、每一个像素、每一段动画都用代码描述出来然后交给浏览器或者渲染引擎去执行。这条路线的核心工具组合就是Claude Opus 这类强代码能力的大模型 JavaScript/Canvas/Three.js 这套前端渲染栈。你描述需求模型写代码代码在浏览器里跑起来录屏或者直接导出一条视频就出来了。听起来有点绕但实测下来对于数据可视化、动态图表、粒子动画、文字动效、产品演示这几类内容它的效率比手动剪辑高出一个数量级。我为什么要专门写这一篇因为过去几个月我用这套方法做了十几个项目踩了不少坑也总结出了一些可复用的套路。网上讲“AI写代码”的内容很多但大多停留在“让模型写个贪吃蛇”这种层面真正把它当成视频生产工具来用的系统性经验少得可怜。这篇文章就是把我自己的实操路径完整拆开包括7 类技术路线怎么选、5 个真实案例怎么落地、提示词框架怎么搭全部讲透。适合谁看如果你是会一点 JavaScript、想做数据动画或者产品演示的开发者这篇能帮你省掉大量试错时间如果你是设计师或者内容创作者完全不懂代码也能看懂思路知道该往哪个方向找工具、找合作如果你已经在用 AI 写代码但还没想过把它用在视频上那这篇可能会给你打开一个新方向。核心关键词先摆出来Claude Opus、提示词框架、JavaScript、Canvas、Three.js。这五个词基本覆盖了整条技术链路——模型负责生成逻辑JavaScript 负责调度Canvas 负责 2D 绘制Three.js 负责 3D 场景提示词框架负责把人的意图翻译成模型能稳定执行的指令。下面进入正题我会先讲整体设计思路再拆技术路线然后上案例最后给提示词框架和避坑清单。2. 整体设计思路把视频当成一个“可执行的程序”2.1 核心思路视频即代码帧即状态传统视频的本质是一串连续的图片每秒 24 帧或 30 帧播放器按顺序刷过去人眼就看到了运动。用代码做视频本质没变只是这些图片不再由摄像机拍摄而是由程序在每一帧根据当前状态重新绘制。这个思路一旦确立很多事情就顺了。你不再需要“剪辑”你需要的是定义状态如何随时间变化。比如一个柱状图从 0 涨到目标值传统做法是打关键帧、调缓动曲线代码做法是写一个progress变量从 0 到 1每帧根据progress计算柱子高度然后重绘。缓动曲线就是一个数学函数easeOutCubic(t)一行搞定。提示这个思维转换是整条路线的门槛。如果你一直想着“怎么剪”会很难受一旦转成“怎么描述状态变化”后面全是顺的。为什么选 JavaScript 而不是 Python因为浏览器就是最好的实时预览器。Python 做视频渲染要么用 moviepy 这种偏剪辑的库要么上 OpenGL调试成本高。而 JavaScript Canvas/Three.js写完刷新页面就能看到效果改一行代码立刻反馈这个迭代速度是视频生产里最宝贵的东西。Claude Opus 对 JavaScript 生态的熟悉程度也明显更高生成的代码开箱即用率更高。2.2 方案选型为什么是 Canvas 和 Three.js 这两条腿前端渲染视频绕不开两个东西Canvas 2D和WebGLThree.js 是它的封装。它们的分工很清晰。Canvas 2D 适合平面图形、文字、图表、粒子、路径动画。它的 API 简单直接fillRect、arc、fillText画什么就是什么。性能上几千个粒子没问题上万个就吃力。它的优势是可控性强、调试直观你画错了立刻能看出来。Three.js 适合3D 场景、空间运动、光照材质、复杂几何体。它把 WebGL 那套繁琐的着色器、缓冲区、矩阵运算封装成了Scene、Camera、Mesh这些直观概念。做产品 3D 展示、空间数据可视化、抽象几何动画Three.js 是首选。代价是学习曲线陡一点性能调优也更复杂。我的经验是能用 Canvas 2D 解决的不要上 Three.js。很多人一上来就想搞 3D结果光调相机和光照就耗掉一半时间。实际上大量视频需求——数据图表、文字动效、2D 粒子——Canvas 2D 完全够用而且代码量少、出错率低。只有当内容本身有空间维度时才值得上 Three.js。2.3 模型选型Claude Opus 在这条链路里的定位为什么强调 Claude Opus因为在这条链路里模型要干的活不是“写个函数”而是理解一个动态视觉意图并把它翻译成结构化的、可运行的、性能合理的代码。这对模型的代码能力、空间想象力、以及对 Canvas/Three.js API 的熟悉度要求都很高。我对比过几个模型在这类任务上的表现。Claude Opus 的优势在于生成的代码结构清晰、注释到位、边界处理周全尤其是涉及动画循环、状态管理、性能优化这些容易出错的地方它很少写出那种“能跑但一跑就卡”的代码。另外它对提示词里的视觉描述理解得比较准你说“粒子从中心向外扩散并逐渐透明”它基本能一次到位不需要来回改。当然模型只是工具关键还是你怎么描述需求。这就引出了提示词框架后面会专门讲。3. 七类技术路线拆解从静态图到交互式视频3.1 路线一Canvas 2D 逐帧绘制最基础也最万能这是最朴素的路线。一个canvas元素一个requestAnimationFrame循环每帧清空画布、根据当前时间计算状态、重新绘制。所有 2D 视频内容都可以用这条路实现。核心代码骨架大概是这样const canvas document.getElementById(stage); const ctx canvas.getContext(2d); const duration 5000; // 视频总时长 5 秒 let startTime null; function draw(timestamp) { if (!startTime) startTime timestamp; const elapsed timestamp - startTime; const progress Math.min(elapsed / duration, 1); ctx.clearRect(0, 0, canvas.width, canvas.height); // 根据 progress 绘制当前帧 renderFrame(ctx, progress); if (progress 1) { requestAnimationFrame(draw); } } requestAnimationFrame(draw);这个骨架的关键点是用progress0 到 1驱动一切。所有动画元素都从progress推导出来这样视频的时长、节奏完全可控想导出就固定每帧的progress值逐帧渲染想预览就实时跑。注意requestAnimationFrame的回调时间戳是毫秒级的但不同设备刷新率不同60Hz、120Hz所以绝对不能用帧数当时间必须用真实时间戳算progress否则在不同设备上速度会不一样。3.2 路线二Canvas 离屏渲染 逐帧导出做真正的视频文件路线一是在浏览器里实时播放但你要的是一个.mp4文件怎么办答案是离屏渲染 逐帧导出。思路是不依赖requestAnimationFrame而是自己控制时间轴每帧手动设置progress渲染到 canvas然后用canvas.toDataURL()或者canvas.toBlob()把这一帧存下来。所有帧存完后用MediaRecorder或者后端工具合成视频。async function exportFrames(fps, duration) { const totalFrames fps * (duration / 1000); const frames []; for (let i 0; i totalFrames; i) { const progress i / (totalFrames - 1); renderFrame(ctx, progress); const blob await new Promise(r canvas.toBlob(r, image/png)); frames.push(blob); } return frames; }这里有个坑toBlob是异步的而且每帧都要等它完成所以导出速度比实时播放慢很多。一个 10 秒 30fps 的视频300 帧可能要跑一两分钟。解决办法是用OffscreenCanvas在 worker 里渲染不阻塞主线程或者干脆接受这个速度毕竟导出是一次性的。提示如果只是要发到社交平台其实录屏往往比逐帧导出更省事。用 OBS 或者系统自带录屏全屏播放一遍直接得到视频文件画质损失可接受效率高得多。逐帧导出适合对画质和帧率有严格要求的场景。3.3 路线三Three.js 场景动画做有空间感的视频当内容涉及 3D 物体、空间运动、光照变化时Three.js 就上场了。它的核心是场景图 相机 渲染循环。const scene new THREE.Scene(); const camera new THREE.PerspectiveCamera(75, w / h, 0.1, 1000); const renderer new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(w, h); const geometry new THREE.BoxGeometry(1, 1, 1); const material new THREE.MeshStandardMaterial({ color: 0x4a90d9 }); const cube new THREE.Mesh(geometry, material); scene.add(cube); const light new THREE.DirectionalLight(0xffffff, 1); light.position.set(5, 5, 5); scene.add(light); camera.position.z 5; function animate(progress) { cube.rotation.x progress * Math.PI * 2; cube.rotation.y progress * Math.PI * 2; renderer.render(scene, camera); }Three.js 做视频的优势是视觉冲击力强一个旋转的 3D 物体比 2D 图形更容易抓住眼球。但代价是性能开销大尤其是复杂场景帧率容易掉。做视频时如果掉帧导出的画面就会卡顿所以场景复杂度要控制。注意Three.js 的渲染是异步的renderer.render调用后 GPU 可能还没画完。逐帧导出时要在render后加一个requestAnimationFrame或者renderer.readRenderTargetPixels来确保这一帧真的画完了再截图否则会截到上一帧或者空白。3.4 路线四SVG CSS 动画轻量级文字动效首选不是所有视频都需要 Canvas。如果内容主要是文字、图标、简单图形的运动SVG CSS 动画是更轻的选择。它的优势是矢量、清晰、代码量小而且可以直接用 CSS 的transition和animation不用手写渲染循环。svg viewBox0 0 400 200 text x50 y100 classtitleHello/text /svg style .title { font-size: 48px; opacity: 0; transform: translateY(20px); animation: fadeUp 1s ease-out forwards; } keyframes fadeUp { to { opacity: 1; transform: translateY(0); } } /style这条路线的局限是控制粒度粗CSS 动画的时间轴不好精确控制做复杂的时间编排会很难受。它适合片头、字幕、简单转场这类独立的小片段然后和其他路线的内容合成。3.5 路线五数据驱动动画图表视频的利器数据可视化视频是代码做视频的杀手级应用。传统做法是用 Excel 或者图表工具生成静态图再手动做动画代码做法是数据直接驱动图形改数据就改视频。核心思路是把数据数组映射到图形属性上用progress控制映射的完成度。const data [30, 55, 80, 45, 90, 65]; const maxValue Math.max(...data); function renderBars(ctx, progress) { const barWidth 40; const gap 20; data.forEach((value, i) { const targetHeight (value / maxValue) * 200; const currentHeight targetHeight * easeOutCubic(progress); const x 50 i * (barWidth gap); const y 250 - currentHeight; ctx.fillStyle #4a90d9; ctx.fillRect(x, y, barWidth, currentHeight); }); } function easeOutCubic(t) { return 1 - Math.pow(1 - t, 3); }这条路线的价值在于可复用。你写好一个图表动画模板换一组数据就是一条新视频。做数据类内容账号的话这套东西能极大提升产能。3.6 路线六图片序列 Canvas 合成把现有素材用起来有时候你手头有现成的图片素材比如产品截图、照片、设计稿想做成视频。这时候可以用 Canvas 把图片序列合成起来加上转场、缩放、位移。const images [img1, img2, img3]; function renderSlideshow(ctx, progress) { const totalSegments images.length; const segmentProgress progress * totalSegments; const currentIndex Math.min(Math.floor(segmentProgress), totalSegments - 1); const localProgress segmentProgress - currentIndex; ctx.globalAlpha 1 - localProgress; ctx.drawImage(images[currentIndex], 0, 0, w, h); if (currentIndex 1 images.length) { ctx.globalAlpha localProgress; ctx.drawImage(images[currentIndex 1], 0, 0, w, h); } }这里的关键是图片加载是异步的必须等所有图片onload之后再开始渲染否则会画到空白。另外图片尺寸和画布尺寸不一致时要算好缩放和裁剪避免变形。3.7 路线七混合渲染Canvas 和 Three.js 叠加最复杂的路线是混合渲染底层用 Three.js 画 3D 场景上层用 Canvas 2D 叠加文字和图表。这需要两个 canvas 元素叠在一起或者用 Three.js 的CSS2DRenderer来渲染 HTML 层。div styleposition: relative; canvas idthree-layer styleposition: absolute; top: 0;/canvas canvas idoverlay-layer styleposition: absolute; top: 0;/canvas /div这条路线的价值是兼顾视觉冲击和信息传达。3D 场景负责吸引注意力2D 文字负责传递信息。但复杂度也最高两个渲染循环要同步性能要平衡调试成本翻倍。我的建议是非必要不上混合能用单一渲染层解决就别叠。4. 五个案例拆解从需求到成品的完整路径4.1 案例一动态柱状图数据视频需求把一组季度销售数据做成 15 秒的柱状图动画柱子从底部升起数值同步跳动最后定格。技术路线Canvas 2D 逐帧绘制 数据驱动。实现要点柱子的高度用easeOutCubic缓动数值用Math.round取整后逐帧更新。为了让数值跳动有节奏感我用了分段缓动——前 70% 时间柱子升起后 30% 时间数值从 0 跳到目标值。function renderFrame(ctx, progress) { ctx.clearRect(0, 0, w, h); const barProgress Math.min(progress / 0.7, 1); const valueProgress Math.max((progress - 0.7) / 0.3, 0); data.forEach((item, i) { const height (item.value / maxValue) * 300 * easeOutCubic(barProgress); const x 80 i * 100; const y 400 - height; ctx.fillStyle #4a90d9; ctx.fillRect(x, y, 60, height); const displayValue Math.round(item.value * easeOutCubic(valueProgress)); ctx.fillStyle #333; ctx.font 20px sans-serif; ctx.textAlign center; ctx.fillText(displayValue, x 30, y - 15); }); }踩坑记录一开始我没做分段柱子和数值同时动结果数值跳得太快观众根本看不清。后来改成先升柱子、再跳数值节奏就舒服了。这个经验告诉我动画的时间分配比动画本身更重要。4.2 案例二粒子扩散文字动效需求品牌名从中心以粒子形式扩散出现粒子有拖尾效果最后聚合成文字。技术路线Canvas 2D 粒子系统。实现要点先在一个离屏 canvas 上画出文字读取像素数据把有颜色的像素位置作为粒子的目标位置。粒子从中心随机位置出发用缓动飞向目标位置。// 读取文字像素 const offCanvas document.createElement(canvas); const offCtx offCanvas.getContext(2d); offCtx.font bold 120px sans-serif; offCtx.fillText(BRAND, 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 alpha imageData.data[(y * imageData.width x) * 4 3]; if (alpha 128) targets.push({ x, y }); } }每个粒子有起始位置中心随机和目标位置文字像素点用progress插值。拖尾效果靠每帧不完全清空画布而是用半透明黑色覆盖形成残影。踩坑记录粒子数量太多会卡。我一开始每个像素都取结果上万个粒子帧率掉到 10。后来改成每 4 个像素取一个粒子数降到两千多效果几乎没差别帧率回到 60。这个“采样降密度”的技巧在粒子系统里非常常用。4.3 案例三Three.js 产品 3D 展示需求一个产品模型在空间中旋转展示配合光照变化和背景渐变20 秒。技术路线Three.js 场景动画。实现要点模型用GLTFLoader加载相机用PerspectiveCamera光照用DirectionalLightAmbientLight组合。旋转用progress驱动背景渐变用一个大的球体或者scene.background设置。const loader new GLTFLoader(); loader.load(product.glb, (gltf) { const model gltf.scene; scene.add(model); function animate(progress) { model.rotation.y progress * Math.PI * 2; const lightIntensity 0.5 Math.sin(progress * Math.PI) * 0.5; directionalLight.intensity lightIntensity; renderer.render(scene, camera); } });踩坑记录GLTF 模型加载是异步的如果不等加载完就开始渲染会渲染出空场景。另外模型的大小和位置需要手动调整model.scale.set()和model.position.set()要反复试。还有一个大坑是光照强度Three.js 的光照默认是物理正确的强度值要设得比较大才有效果我一开始设 1画面几乎全黑后来调到 3 才正常。4.4 案例四图片轮播转场视频需求5 张产品图每张停留 2 秒之间用滑动转场总时长 12 秒。技术路线Canvas 2D 图片序列合成。实现要点把时间轴分成 5 段每段 2.4 秒含 0.4 秒转场。转场时两张图同时绘制一张滑出一张滑入。function renderSlideshow(ctx, progress) { const segmentDuration 1 / 5; const transitionRatio 0.2; const segmentIndex Math.min(Math.floor(progress / segmentDuration), 4); const localProgress (progress - segmentIndex * segmentDuration) / segmentDuration; const currentImg images[segmentIndex]; ctx.drawImage(currentImg, 0, 0, w, h); if (localProgress 1 - transitionRatio segmentIndex 4) { const transProgress (localProgress - (1 - transitionRatio)) / transitionRatio; const nextImg images[segmentIndex 1]; const offset w * (1 - easeOutCubic(transProgress)); ctx.drawImage(nextImg, offset, 0, w, h); } }踩坑记录图片的宽高比和画布不一致时直接drawImage会拉伸变形。正确做法是计算等比缩放后的尺寸然后居中裁剪或留白。我写了一个drawImageCover函数专门处理这个后面所有项目都复用了。4.5 案例五实时数据仪表盘动画需求模拟一个实时数据仪表盘指针摆动、数字滚动、环形进度条10 秒循环。技术路线Canvas 2D 多组件组合。实现要点这个案例的难点是多个动画组件的时间同步。我用一个全局progress驱动所有组件每个组件内部再根据自己的节奏做局部缓动。function renderDashboard(ctx, progress) { // 环形进度条 const ringProgress easeOutCubic(Math.min(progress / 0.5, 1)); ctx.beginPath(); ctx.arc(cx, cy, radius, -Math.PI / 2, -Math.PI / 2 Math.PI * 2 * ringProgress); ctx.strokeStyle #4a90d9; ctx.lineWidth 12; ctx.stroke(); // 指针摆动 const needleAngle -Math.PI / 2 Math.sin(progress * Math.PI * 4) * 0.3 progress * Math.PI * 0.8; ctx.beginPath(); ctx.moveTo(cx, cy); ctx.lineTo(cx Math.cos(needleAngle) * 80, cy Math.sin(needleAngle) * 80); ctx.stroke(); // 数字滚动 const displayValue Math.round(1000 * easeOutCubic(progress)); ctx.fillText(displayValue, cx, cy 60); }踩坑记录指针摆动如果只用progress线性驱动会很死板。我加了一个Math.sin的抖动模拟真实仪表的惯性效果立刻生动了。这个技巧叫叠加噪声在动画里非常实用能让机械的运动变得有生命感。5. 可复用提示词框架让模型稳定输出可用代码5.1 框架结构五段式描述法跟模型要代码最忌讳的就是“帮我写个动画”这种模糊指令。模型只能猜猜出来的东西十有八九要返工。我总结了一个五段式提示词框架实测下来一次通过率能到 80% 以上。第一段技术栈声明。明确告诉模型用什么技术不要让它自己选。使用纯 JavaScript Canvas 2D不依赖任何第三方库。第二段画布规格。尺寸、背景、帧率。画布尺寸 1920x1080背景色 #0a0a0a目标帧率 60fps。第三段视觉描述。这是最关键的一段要用空间和时间的语言描述画面。画面中央有一个半径 200px 的圆环圆环从 12 点方向开始顺时针绘制 在 2 秒内从 0 度增长到 360 度。圆环颜色从 #4a90d9 渐变到 #7b61ff。 圆环内部有一个数字从 0 增长到 100字体 72px 粗体白色。第四段时间轴。明确每个阶段的时间点和持续时长。总时长 5 秒。0-2 秒圆环绘制2-3 秒数字跳动3-5 秒整体淡出。第五段代码约束。性能、结构、注释要求。使用 requestAnimationFrame 驱动用 progress0-1控制所有动画。 代码分函数组织关键逻辑加注释。避免每帧创建新对象。这五段写清楚模型基本能一次给出可运行的代码。我试过把同样的需求用模糊指令和五段式分别发给模型模糊指令改了 4 轮才勉强能用五段式第一版就能跑。5.2 视觉描述的技巧用“状态变化”代替“动画效果”很多人描述需求时会说“让它动起来”“加个淡入效果”这种描述模型理解不了。正确的做法是描述状态如何随时间变化。不要说“加个淡入”要说“透明度从 0 线性变化到 1持续 0.5 秒”。不要说“让它转起来”要说“旋转角度从 0 线性增长到 2π持续 3 秒”。不要说“弹一下”要说“缩放从 1 到 1.2 再到 1用 easeOutBack 缓动持续 0.4 秒”。这个转换需要练习但一旦掌握你和模型的沟通效率会质变。本质上你是在用数学语言描述视觉而模型最擅长的就是数学。5.3 迭代策略小步快跑每次只改一个变量拿到模型的第一版代码后不要急着大改。我的做法是先跑起来看效果然后每次只调一个参数。比如圆环动画第一版跑起来发现转得太快我只改时长参数从 2 秒改成 3 秒其他不动。改完再看如果颜色不对再只改颜色。这样每次改动的影响是可控的出了问题也知道是哪一步引入的。提示把模型的输出当成一个可调参数的黑盒而不是需要理解每一行的代码。你不需要看懂所有实现细节只需要知道改哪个参数能得到想要的效果。这能极大降低使用门槛。5.4 常见提示词模板直接抄作业模板一数据图表动画使用纯 JavaScript Canvas 2D 实现一个柱状图动画。 画布 1920x1080背景白色。 数据[30, 55, 80, 45, 90, 65]对应标签 [A, B, C, D, E, F]。 柱子从底部升起用 easeOutCubic 缓动持续 1.5 秒。 柱子颜色 #4a90d9宽度 60px间距 40px。 柱子顶部显示数值字体 24px颜色 #333。 总时长 3 秒最后 0.5 秒整体保持静止。 用 progress0-1驱动requestAnimationFrame 循环。模板二粒子文字动效使用纯 JavaScript Canvas 2D 实现粒子文字动画。 画布 1920x1080背景黑色。 文字内容 HELLO字体 bold 200px sans-serif。 粒子从画面中心随机位置出发飞向文字像素位置持续 2 秒。 粒子颜色 #4a90d9大小 2px带拖尾效果每帧用半透明黑色覆盖。 文字像素采样间隔 4px避免粒子过多。 总时长 4 秒最后 2 秒粒子保持静止。模板三Three.js 3D 旋转使用 Three.js 实现一个立方体旋转动画。 场景背景色 #1a1a2e相机 PerspectiveCamera位置 (0, 0, 5)。 立方体尺寸 1x1x1材质 MeshStandardMaterial颜色 #4a90d9。 方向光位置 (5, 5, 5)强度 3环境光强度 0.5。 立方体绕 Y 轴旋转从 0 到 2π持续 4 秒线性缓动。 总时长 4 秒用 progress 驱动。这三个模板覆盖了最常见的需求改改参数就能用。我建议你把这几个模板存下来下次直接改数据和时间效率极高。6. 常见问题与排查技巧实录6.1 画面卡顿、帧率低怎么办这是最常见的问题。原因通常有三个绘制对象太多、每帧创建新对象、没有用离屏渲染。排查顺序先看粒子或图形数量超过 5000 个基本就会卡解决办法是降低采样密度或者用Path2D批量绘制。再看有没有在renderFrame里new对象比如每帧new Date()、new Array()这些都会触发垃圾回收造成卡顿解决办法是把对象提到循环外复用。最后看有没有频繁的getImageData或putImageData这两个操作极慢能不用就不用。提示Chrome DevTools 的 Performance 面板可以录制一段动画看每一帧的耗时。如果某一帧特别长就是那里有问题。6.2 导出的视频和预览不一致这个问题通常出在时间基准上。预览时用requestAnimationFrame的时间戳导出时用固定步长两者的progress计算方式不同导致动画节奏不一样。解决办法是统一时间基准。预览和导出都用同一个renderFrame(progress)函数只是驱动方式不同。预览时progress elapsed / duration导出时progress frameIndex / totalFrames。这样只要duration和totalFrames对应效果就一致。6.3 图片加载导致首帧空白图片是异步加载的如果不等加载完就渲染第一帧会是空白。解决办法是用Promise.all等所有图片加载完再启动渲染循环。const imageUrls [a.jpg, b.jpg, c.jpg]; const images await Promise.all(imageUrls.map(url { return new Promise((resolve) { const img new Image(); img.onload () resolve(img); img.src url; }); })); // 所有图片加载完开始渲染6.4 文字模糊、锯齿严重Canvas 默认的分辨率是 CSS 像素在高分屏上会模糊。解决办法是把 canvas 的width和height设成 CSS 尺寸的 2 倍然后用ctx.scale(2, 2)缩放坐标系。const dpr window.devicePixelRatio || 2; canvas.width w * dpr; canvas.height h * dpr; canvas.style.width w px; canvas.style.height h px; ctx.scale(dpr, dpr);这样绘制出来的文字和图形在高分屏上会清晰很多。导出视频时也建议用 2 倍分辨率后期再压缩。6.5 常见问题速查表问题现象可能原因解决办法画面卡顿绘制对象过多降低采样密度用 Path2D 批量绘制帧率不稳每帧创建新对象对象提到循环外复用导出与预览不一致时间基准不同统一用 renderFrame(progress)首帧空白图片未加载完Promise.all 等待加载文字模糊分辨率不足用 devicePixelRatio 放大画布3D 场景全黑光照强度太低提高光照强度到 3 以上粒子拖尾不生效每帧完全清空用半透明覆盖代替 clearRect动画速度不对用帧数当时间用真实时间戳算 progress6.6 独家避坑技巧技巧一用performance.now()而不是Date.now()。Date.now()精度是毫秒performance.now()是亚毫秒动画更平滑。技巧二缓动函数自己写不要引库。easeOutCubic、easeInOutQuad这些就几行代码引一个库反而增加体积和不确定性。技巧三颜色用 HSL 而不是 HEX。做颜色渐变时HSL 的色相、饱和度、亮度可以独立插值效果比 HEX 插值自然得多。技巧四导出前先录屏预览一遍。录屏能发现很多实时预览时注意不到的问题比如节奏太快、某个元素被遮挡、颜色对比度不够。技巧五把常用的绘制函数封装成工具库。比如drawRoundedRect、drawText、easeOutCubic每个项目都复用越用越顺手。7. 工具链与工作流建议7.1 开发环境越简单越好我的开发环境就是一个 HTML 文件加一个 JS 文件用浏览器直接打开。不需要 webpack、vite 这些构建工具因为代码量不大而且构建工具会增加调试的复杂度。Three.js 用 CDN 引入或者下载下来放本地。script srchttps://cdn.jsdelivr.net/npm/three0.160.0/build/three.min.js/script提示Three.js 的版本更新很快API 有时会变。建议锁定一个版本不要用latest否则某天打开项目发现跑不起来了。7.2 录屏工具OBS 是首选导出视频最省事的方式是录屏。OBS 免费、跨平台、可调参数多。设置里把输出分辨率设成和画布一致帧率 60码率 8000kbps 以上画质就够用了。录制时全屏播放动画录完直接得到 mp4。如果对画质要求极高可以用逐帧导出 ffmpeg 合成。ffmpeg 命令ffmpeg -framerate 60 -i frame_%04d.png -c:v libx264 -pix_fmt yuv420p output.mp47.3 工作流从需求到成品的五步第一步明确需求。想清楚视频要传达什么信息时长多少什么风格。第二步写提示词。用五段式框架把技术栈、画布规格、视觉描述、时间轴、代码约束写清楚。第三步生成代码并预览。把模型输出的代码存成 HTML浏览器打开看效果。第四步迭代调整。每次只改一个参数直到满意。第五步导出。录屏或者逐帧导出得到最终视频。这个流程走顺了一条 15 秒的视频从需求到成品熟练的话半小时以内能搞定。相比传统剪辑效率提升非常明显尤其是需要反复修改的时候改一行代码比重新剪辑快得多。8. 一些个人体会用代码做视频这件事我最大的感受是它把视频生产从“手艺”变成了“工程”。传统剪辑依赖手感和经验同样的效果不同人做出来差别很大代码做视频效果是可复现的、可参数化的、可批量生产的。你写好一个模板换数据就是新视频这种产能是手工剪辑比不了的。另一个体会是模型的能力边界在快速扩展。我刚开始用的时候稍微复杂的动画就要来回改好几轮现在 Claude Opus 这类模型只要提示词写清楚基本一次到位。这意味着提示词能力正在成为核心竞争力——同样的模型会写提示词的人产出效率是别人的好几倍。最后分享一个小技巧把每次成功的提示词和代码存下来建一个自己的模板库。我现在有二十多个模板覆盖柱状图、折线图、粒子、3D 旋转、文字动效这些常见需求。新项目来了先翻模板库能复用就复用不能复用就在模板基础上改。这个习惯让我的启动成本几乎降到了零。这个方向后续还能怎么扩展我最近在试的是把实时数据接进来让视频内容随数据变化自动更新比如股票走势、天气变化、网站访问量。技术上就是把数据源换成 API 调用定时刷新重新渲染。这条路走通了视频就从“一次性内容”变成了“持续更新的内容”价值又不一样了。
阅读完成 · 觉得有帮助?