Scroll Video Website
何时使用
当用户希望实现以下目标时,请使用此技能:
- 构建一个Apple风格的、由视频驱动的滚动动画;
- 创建一个滚动控制或滚动擦洗的视频网站;
- 将产品视频转化为电影感的落地页;
- 构建由视频驱动的滚动叙事体验;
- 将视频转换为用于滚动动画的优化图像序列;
- 创建一个基于Canvas的、由滚动控制的产品展示;
- 优化现有的滚动视频帧序列。
请勿将其用于普通的自动播放视频背景或不相关的滚动效果。
创建一个极简的全页体验,例如产品动画查看器:页面本身提供滚动距离,同时一个固定位置的画布填满视口,并根据用户视频导出的帧序列进行擦洗。动画本身就是界面,而不是传统落地页背后的背景。
调用方式
此技能有两种模式:从视频构建滚动视频网站,或优化现有帧序列。如果第一个参数是 optimize,请遵循 references/optimize.md 指南,不要运行下面的构建工作流。
将此简洁形式视为完整的构建请求:
$scroll-video-website <path/to/video.mp4>
视频路径必须是技能名称后的第一个参数。路径之后的所有内容都是可选的用户指示,用于样式、布局、文案或行为:
$scroll-video-website ./media/product-reveal.mp4
$scroll-video-website ./media/product-reveal.mp4 使用黑色背景和紧凑的排版
第一个示例的意思是:导出帧序列并构建下面描述的完整默认滚动动画网站。第二个示例中,保留相同的视频驱动动画架构,同时遵循添加的视觉指示。让使用该技能的AI代理解释并实现提示的其余部分;此技能不定义样式选项的单独命令语法。
将视频路径解释为相对于当前工作目录的路径,除非它是绝对路径。如果路径包含空格,用户应将其引起来。首先检查确切路径。如果文件名不存在于当前目录,则在当前项目中搜索精确的文件名匹配,同时排除依赖项、构建、缓存和版本控制目录。当恰好找到一个匹配项时,自动继续。如果没有匹配项或多个匹配项,请询问用户确切路径。<path/to/video.mp4> 或 {video_path} 中的大括号表示占位符,不是实际路径的一部分。不要要求或解释 use 关键字。
使用以下精确命令优化现有序列:
$scroll-video-website optimize
optimize 命令不带路径或其他参数。检查当前项目是否包含生成的帧序列。当可以唯一识别一个序列时,继续执行。如果可以识别多个序列,则列出它们并询问要优化哪一个。如果找不到或无法自信地识别任何序列,则告知用户未找到 frames 文件夹并停止。
建立项目与源视频
在编辑之前:
- 检查项目结构、脚本、框架、样式、路由、资产约定和本地说明。
- 使用上面的调用规则定位用户提供的视频。如果没有提供视频或路径,并且无法自信地识别出任何一个,请在实施前询问用户。
- 重用现有的工作栈。如果项目已经正常工作,请不要搭建一个替代项目。
- 在可用时,使用
ffprobe检查视频的尺寸、时长、帧率、帧数和文件大小。 - 保留不相关的行为,只做最小且连贯的必要更改。
不要创建替代素材或替换提供的素材。除非构建需要,否则避免复制源视频。
将视频转换为帧序列
默认的渲染路径是带编号的 WebP 帧序列,而不是 video.currentTime。将帧提取到项目的公共/静态资产区域,并使用零填充名称,例如 frames/frame_0001.webp。
- 保持源视频的节奏,最高 30 fps。对于较长的片段,采样至大约 240–300 帧,以便有效载荷保持实用。
- 保持源视频的宽高比。使用合理的 WebP 质量设置(大约 78–82),并在实施后检查结果,而不是假设设置是足够的。
- 在实现中记录实际提取的帧计数;不要留下猜测的常量。
- 优先使用
ffmpeg进行提取。一个代表性的命令是ffmpeg -i input.mp4 -vf fps=30 -c:v libwebp -quality 80 frames/frame_%04d.webp;根据输入、fps、输出路径和现有项目工具进行调整。 - 不要删除或覆盖不相关的资产。如果目标位置已存在序列,请先确认它属于此动画,然后再替换它。
仅当帧提取不可用或生成的帧有效载荷明显不合适时,才使用直接的暂停视频查找方式。如果回退使用此方式,请解释其中的权衡,并保留相同的固定阶段、双向、平滑滚动行为。
匹配参考体验
当用户没有提供额外的布局或文案指示时,生成一个纯画布页面:
- 白色页面,固定且溢出隐藏的舞台,覆盖
100vw乘以100vh; - 足够高的不可见文档高度以实现有意的擦洗,对于大约8秒或大约240帧的源视频,大约为
400vh; - 没有导航栏、卡片、标题、行动号召、渐变或编造的营销文案;
- 一个填满舞台的画布,使用
cover几何绘制每一帧,并在两个轴向上居中; - 仅在首帧准备就绪后,进行一个短暂的透明度渐显,防止空白帧闪现;
- 仅在适合素材的情况下使用克制的图像处理,例如白色背景、轻微对比度调整,或对白色背景的产品渲染使用
mix-blend-mode: multiply。
如果用户要求添加内容,请保持内容稀疏,并将其放置在动画中有意的时刻之上或之间,而不减少画布的视觉主导地位。当原生 Canvas 和浏览器 API 足够时,不要引入动画依赖库。
实现画布擦洗
使用一个固定或粘性定位的舞台,包含一个 aria-hidden 的画布。根据设备像素比调整其后备缓冲区大小,上限设为 2,同时保持绘制测量使用 CSS 像素。在调整大小时,先重置画布变换,然后再应用新的 DPR 缩放,以便缩放不会累积。
将限制后的总页面进度映射到完整序列:
const range = document.documentElement.scrollHeight - innerHeight
const progress = range > 0 ? Math.min(1, Math.max(0, scrollY / range)) : 0
targetFrame = progress * (frameCount - 1)
向前滚动必须推进动画,反向滚动必须反转动画。滚动和调整大小处理程序应仅更新廉价测量值或目标帧。运行单个 requestAnimationFrame 循环,并使用与帧率无关的指数插值来平滑浮点播放头:
const dt = Math.min((now - lastTime) / 1000, 0.1)
const alpha = 1 - Math.exp(-dt * 9)
currentFrame += (targetFrame - currentFrame) * alpha
在播放头的两侧绘制整数帧,并通过小数部分混合到下一帧。首先清除或绘制画布背景,绘制后重置 globalAlpha,并在调整大小时重新绘制。
渐进式加载帧
不要让整个序列阻塞首次绘制:
- 首先加载第 1 帧,绘制它,并显示画布。
- 在时间轴上排队均匀分布的帧,例如每隔八帧一帧,以便在早期寻找时有近似的备用帧。
- 排队所有剩余的帧。
分别跟踪已请求和已加载的帧,以便优先级传递和剩余帧传递绝不会重复请求同一张图片。当请求的帧仍在加载时,绘制最近加载的帧。处理加载失败的图像,不要阻塞序列的其余部分。
使用适用于项目基础路径和部署目标的资产 URL 策略。当所属组件卸载时,清理滚动、调整大小、媒体查询监听器、观察器和动画帧。
响应式行为与可访问性
在小屏幕上保持 cover 渲染并接受有意的裁剪;不要扭曲图像。在桌面和移动尺寸下检查重要主体的位置。如果添加了可见内容,请考虑移动视口变化和安全区域。
对于 prefers-reduced-motion: reduce,停止擦洗并保持第一个合适的帧。确保页面上的任何内容都是可访问的,不要劫持滚动。装饰性画布不应为屏幕阅读器创建冗余内容。
验证结果
运行项目的检查,并在可用时在浏览器中检查结果。确认:
- 第一个和最后一个滚动位置分别到达第一个和最后一个提取的帧;
- 向前和向后滚动的感觉是连续的,没有突然的跳跃、回放、闪烁或可见的空白画布;
- 不会重复请求帧,对于缺失或加载慢的帧,使用附近已加载的备用帧;
- cover 裁剪、DPR 调整和画布缩放在桌面和移动尺寸下正常工作;
- 减少动效模式持有一个静态帧,并且组件清理完整;
- 生成的序列具有预期的数量、尺寸、顺序和可接受的总大小;
- 项目中不相关的其他行为仍然有效。
最后,总结体验、提取的帧数和有效载荷、执行的检查以及任何剩余的源媒体限制。