一圈彩色玻璃向画面深处卷去。仔细看,每块玻璃里都有自己的画面,有的是照片,有的是从视频里截取的一帧。2.7 秒后,这些在隧道里露过面的素材,开始按节拍全屏播放。
这是我在开源浏览器视频编辑器 Timeline Studio 里做的「回忆漩涡卡点」。取帧、图集合成、时间轴和导出都在仓库里。如果你正在做浏览器端剪辑、视频取帧或 Canvas 特效,可以把下面这套实现当作一个能跑的例子。GitHub 开源仓库 · MartinDelophy/ai-video-editor
逐帧拆解后,我改了素材的组织方式
我最初把同一张照片裁成不同区域,铺到逐圈展开的玻璃片上。动画轮廓已经很接近,画面却少了原片那种“回忆从四周涌过来”的感觉。把参考视频的开场放大后,我才看清玻璃片上的小编号。原来后面要全屏播放的镜头,已经提前在隧道里出现了。
参考视频是 30 fps。去掉开头的 3 帧教程封面后,隧道占 81 帧,也就是 2.7 秒。后面有 48 个镜头,换片间隔大多为 16–18 帧。我们以 106 BPM 作为默认速度,同时保留逐段测得的切点。用户改 BPM 时,开场、切点和转场时长一起按比例缩放。查看模板时间表源码

同一组素材绑定,供开场和正片各用一次
每个素材位都对应后续时间轴上的一个镜头。生成隧道时,照片直接使用,视频则从该镜头实际取用的区间抓一帧。
const sourceTime = getVisualSourceTime(shot, shot.duration / 2);
这里用的是编辑器已有的时间映射。举个例子,镜头取用源视频的第 10–12 秒,以 2 倍速放进时间轴上的 1 秒。时间轴上走到这个镜头的 0.5 秒,取到的是源视频第 11 秒。这样,同一个长视频也可以填进多个素材位,让玻璃片显示它的不同时刻。
取帧前,我们按源文件分组,对采样时间去重、排序。浏览器和素材支持时走 WebCodecs 解码,必要时回退到原生视频元素定位取帧。解码后的画面被裁成统一尺寸,最后排成一张 8 列 × 6 行的纹理图集。查看取帧和图集生成源码
开场播放时,Canvas 只读取一张图集
每格图集是 224 × 280,48 格合在一起是 1792 × 1680。Canvas 根据素材位编号找到源矩形,再把它画进玻璃片的裁剪路径。
const sx = (slotIndex % 8) * 224;
const sy = Math.floor(slotIndex / 8) * 280;
ctx.save();
ctx.clip(panePath);
ctx.drawImage(atlas, sx, sy, 224, 280, x, y, width, height);
ctx.restore();
隧道的纵深由几组随时间变化的参数构成。内外圈有不同的半径,玻璃片分批亮起,每圈还带一点旋转偏移。开场的解码开销集中在应用模板时,播放时主要做图集采样和 Canvas 绘制。
预览和离线导出共用这套绘制逻辑,动画进度取自特效片段在时间轴上的局部时间。暂停后跳到 1.5 秒,或者导出到 1.5 秒,都按同一套参数计算玻璃片的位置。保存 .timeline 工程时,图集会一起打包。后续 48 个镜头仍然可以分别编辑。查看 Canvas 渲染源码

拉下源码,换成自己的素材
下面的命令固定到这篇文章对应的版本。运行后,在编辑器里打开「模板 → 回忆漩涡卡点」,用示例素材生成一次,再换成自己的照片或视频,能直接看到素材绑定怎样同时影响开场和后续镜头。
git clone --branch v1.0.12 --depth 1 https://github.com/MartinDelophy/ai-video-editor.git
cd ai-video-editor
npm ci
npm run dev
这版已经验证了多张照片混用、从同一段视频取不同帧,以及预览和 MP4 导出。默认速度下,导出的视频轨共 897 帧,开场之后接 48 个镜头和 47 个叠化。在线体验 Timeline Studio · 查看 v1.0.12 发布记录
如果你也在做 Web 视频编辑,欢迎来仓库看看这套实现。源码对你有帮助的话,可以点个 Star。如果你有想拆解的剪辑效果,也欢迎开个 Issue,聊聊它的素材组织和时间轴结构。访问 GitHub 仓库 · 提交 Issue