关于 scroll-film
scroll-film 是用于构建滚动控制画布动画的网页技能,可将渲染片段处理为帧序列或可寻址视频,并支持停帧、平滑和减少动态效果。
主要功能
- 01将滚动进度映射到画面帧
- 02制作 WebP 帧序列或 MP4 视频
- 03检测片段衔接处的跳变
- 04为字幕段落设置停帧
- 05支持减少动态效果偏好
适用场景
- 01制作滚动驱动的落地页动画
- 02呈现沿固定镜头路径生成的场景
- 03字幕显示时暂停画面
- 04为手机视口准备独立素材
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ moh4696/ open-3d-skills --skill scroll-film安装后在 Claude Code 中输入/scroll-film确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
agents/openai.yamlassets/scroll-film.jsscripts/prep.py详细介绍
滚动画卷
滚动画卷会在访客滚动浏览一个较长的区块时固定画布,并根据滚动位置选择帧。访客控制镜头,因此即使每一帧都提前渲染好了,效果仍有三维感。当运动过于复杂、不适合实时 3D(生成的世界、液体、人物),且路径固定时,这就是合适的工具。
工具
| 文件 | 功能 |
|---|---|
assets/scroll-film.js | 引擎:固定画布、带停留段的进度到帧映射、帧序列或视频、平滑处理、减少动态效果 |
scripts/prep.py frames | 将片段合并为一个带编号的 WebP 序列和清单 |
scripts/prep.py video | 将片段合并为一个支持即时定位的 MP4 |
scripts/prep.py seams | 为片段之间的每个衔接处评分,并在出现明显跳变时失败 |
需要 ffmpeg 和 ffprobe、Python 3.9+,以及 pip install pillow numpy。制作片段请用 motion-assets;透明主体请用 alpha-cutout;检查结果请用 site-check 和 frame-capture。
生成前先规划
- 按节拍编写故事。 每个场景对应一个节拍:镜头一开始看到什么、镜头移向哪里、访客在那里会读到什么。四到七个节拍足以填满一个落地页。一个节拍对应一个生成的片段(一“段”)。
- 先生成所有关键帧,并确保风格统一。 每一段都在起始帧和结束帧之间生成,而上一段的结束帧就是下一段的起始帧。在生成任何视频前,先用同一个模型和同一个风格参考图生成所有关键帧静帧,并将它们并排排列。静帧之间风格漂移,整部影片的风格也会随之漂移。
- 明确估算预算。 成本大致为静帧费用加上片段数量乘以单个片段的价格。手机竖屏版本会使视频成本翻倍(见“手机”)。投入前先用一张静帧和一个片段进行校准(
motion-assets涵盖模型选择、定价和速率限制)。
构建
-
生成各段,使用第 2 步中的起始帧和结束帧。检查每个衔接处:
python3 scripts/prep.py seams legs/01.mp4 legs/02.mp4 legs/03.mp4低于 4 就看不出来;高于 10 就会出现跳变。若衔接不佳,请以紧接前一段最后一帧的画面为锚点重新生成该段(用 ffmpeg 提取该帧,而不是重复使用静帧:模型会稍微改变色彩和构图)。
-
选择帧序列或视频。
帧序列(默认) 可拖动定位的视频 精确度 每个滚动位置都对应精确帧 定位会稍有延迟,在 Safari 和低端手机上最明显 大小 1280 到 1600 px 时每帧 20 到 60 kB 大约只有前者的三分之一 适用场景 最多约 300 帧 注重体积而非精度的长影片 python3 scripts/prep.py frames legs/*.mp4 site/film/lg --width 1600 --fps 24 python3 scripts/prep.py frames legs/*.mp4 site/film/sm --width 900 --fps 24 --quality 72 python3 scripts/prep.py video legs/*.mp4 site/film.mp4 --width 1920 --gop 4frames会丢弃每个后续片段的第一帧,因为它与前一片段的最后一帧重复。video使用 4 帧 GOP 且不使用 B 帧:普通导出若使用 250 帧 GOP,每次定位都要解码数百帧,导致拖动时卡顿。 -
标记区块。 高度决定影片需要滚动多长距离。大致按影片每秒对应一个视口的滚动距离,会让节奏显得从容;距离更短则会显得仓促。
<section id="film" style="height: 600vh"> <div class="pin" style="position: sticky; top: 0; height: 100vh"> <canvas style="width: 100%; height: 100%; display: block"></canvas> <div class="captions">...</div> </div> </section> -
接入引擎。
import { ScrollFilm } from "./scroll-film.js"; const small = matchMedia("(max-width: 760px)").matches; const film = new ScrollFilm({ section: document.querySelector("#film"), canvas: document.querySelector("#film canvas"), frames: { count: 247, src: (i) => `film/${small ? "sm" : "lg"}/${String(i).padStart(3, "0")}.webp` }, keys: [[0, 0], [0.18, 60], [0.3, 60], [0.62, 160], [0.72, 160], [1, 246]], onUpdate: ({ progress }) => captions(progress), onReady: () => document.body.classList.add("film-ready"), });keys是[progress, frame]对。同一帧对应两个键就会形成停留段:影片会在某个节拍处暂停,同时说明文字滚入和滚出。每个包含文字的节拍都应设置停留段。若没有停留段,文字就会叠在移动的画面上,没人看得清。smooth(默认值 0.18)会让影片平滑地跟随滚动位置,并且不受帧率影响,因此鼠标滚轮的一步步滚动会变成流畅运动,而不是画面跳变。如果页面已经平滑处理滚动(Lenis),请设置smooth: 1。说明文字和图层通过
onUpdate读取progress。在停留段开始时,用一小段范围淡入说明文字,并在停留段结束时淡出;让文字避开画面中最繁忙的区域,必要时在后方加一层柔和的遮罩。
手机
将 16:9 影片居中裁切为手机竖屏画面,通常会裁掉主体。请检查每个节拍的裁切效果。如果不合适,请根据竖屏关键帧生成 9:16 竖屏片段,并将其作为单独的一组发布,通过媒体查询切换;还要提前说明,这会使视频预算翻倍。只有当每个节拍中的主体都处于画面中间三分之一时,居中裁切才合适。
手机也应使用较小的帧集,整部影片的大小应控制在约 8 MB 以内。
加载
- 将第 0 帧作为
<img>放入 HTML 中(或设为画布背景),这样首次绘制时显示的就是影片,而不是空白框。 - 引擎会先加载第 0 帧,再加载最后一帧,然后按由粗到细的顺序加载分布帧(每隔 64 帧、32 帧,依此类推),每次加载 6 帧。访客快速滚动时,总能停在接近已解码帧的位置,并看到画面逐渐变清晰。
- 视频模式会先将整个文件下载为 blob,再开始播放,因此跳转时无需等待网络。
- 请使用支持 Range 请求的主机。
python3 -m http.server不支持此功能,因此它所提供的视频无法跳转;在本地,请使用frame-capture技能中的 serve.py。
减少动态效果
设置 prefers-reduced-motion: reduce 时,引擎会在关键帧之间切换(默认值为 reducedMotion: "steps"),而不是播放视频,因此每个节拍仍会显示对应画面。"still" 则只显示最后一个关键帧。
检查
- 使用
frame-capture技能,以较低帧率录制滚动过程,按秒评估节奏:record.py URL check.mp4 --fps 8 --seconds 20 --scroll "0:0 18:100%"。 - 快速上下拖动:画面不能空白,接缝处不能闪烁,滚动固定应在末尾平滑解除。
- 在限速网络下加载:首次绘制时应显示第 0 帧。
- 检查手机视口和较短的笔记本电脑视口(1280x720):字幕不能遮住主体。