使用指南

常见错误

破坏 Hyperframes 合成的常见陷阱。

这些是 lint 无法捕获的错误。有关自动检查,请运行 npx hyperframes lint(参见 CLI)。

⚠️ Warning

前两个错误——对视频元素尺寸做动画和在脚本中控制媒体播放——是合成出错的最常见原因。如果你的视频看起来不对,首先检查这些。

**症状:** 视频帧停止更新,或浏览器性能严重下降。

原因: GSAP 直接对 <video> 元素的 widthheighttopleft 做动画可能导致浏览器停止渲染帧。

修复前(有问题):

// 直接对视频元素做动画——导致帧渲染停止
l.to("#el-video", { width: 500, height: 280, top: 700, left: 1400 }, 26);

修复后:

<!-- 将视频包装在 div 中并对包装器做动画 -->
<div id="pip-wrapper" style="position: absolute; width: 1920px; height: 1080px;">
<video id="el-video" data-start="0" data-track-index="0"
src="./assets/video.mp4" style="width: 100%; height: 100%;"></video>
</div>
// 对包装器做动画——视频通过 CSS 以 100% 填充它
l.to("#pip-wrapper", { width: 500, height: 280, top: 700, left: 1400 }, 26);

对于画中画等视觉效果,使用非计时的包装器 <div>。对包装器做动画;让视频通过 CSS 填充它。

症状: 音视频播放不同步,或在不应播放时播放。

原因: 在脚本中调用 video.play()video.pause() 或设置 audio.currentTime框架拥有所有媒体播放

修复前(有问题):

// 与框架媒体同步冲突
document.getElementById("el-video").play();
document.getElementById("el-audio").currentTime = 5;

修复后:

// 完全不控制媒体播放。框架会处理它。
// 仅使用 GSAP 做视觉动画:
l.to("#el-video", { opacity: 1, duration: 0.5 }, 0);

框架通过读取 data-startdata-media-startdata-volume 来控制媒体何时以及如何播放。参见合成:两个层次了解 HTML 原语和脚本之间的分离。

症状: 视频播放几秒后停止。时间线显示 8-10 秒,即使视频有几分钟长。

原因: 合成时长等于 GSAP 时间线时长,而非视频上的 data-duration。如果你的最后一个 GSAP 动画在 8 秒结束,合成就是 8 秒长——无论视频源有多长。

修复前(有问题):

// 时间线只有 7.8 秒长——视频在 7.8 秒后被截断
l.to("#lower-third", { left: -640, duration: 0.6 }, 7.2);

修复后:

l.to("#lower-third", { left: -640, duration: 0.6 }, 7.2);

// 将时间线延长到 283 秒以匹配视频长度
l.set({}, {}, 283);

tl.set({}, {}, TIME) 在指定时间添加一个零时长的补间,延长时间线而不影响任何元素。

💡 Tip

快速检查:运行 npx hyperframes compositions 查看每个合成的解析时长。如果比预期短,需要延长你的时间线。

症状: 元素始终可见,忽略其 data-startdata-duration 计时。

原因: class="clip" 属性告诉运行时管理元素的可见性生命周期。没有它,元素将始终被渲染。

修复前(有问题):

<!-- 缺少 class="clip"——此元素始终可见 -->
<h1 id="title" data-start="2" data-duration="5" data-track-index="0">
Hello World
</h1>

修复后:

<!-- 有了 class="clip",运行时仅在 2s 到 7s 之间显示此元素 -->
<h1 id="title" class="clip" data-start="2" data-duration="5" data-track-index="0">
Hello World
</h1>

ℹ️ Note

lint 会捕获这个问题:npx hyperframes lint 会标记缺少 class="clip" 的计时元素。

症状: 带图片的场景预览卡顿。渲染比预期慢。

原因: 源图片分辨率远高于画布。Chrome 在显示前会将图片解码为原始 RGBA 位图,位图大小为 width × height × 4 字节——与磁盘上的文件大小无关。7000×5000 的 JPEG 解码后为 140MB,即使文件只有 2MB。

在 384×1080 区域中显示这样的图片会浪费内存并迫使合成器每帧重新采样一个巨大的纹理。

修复前(臃肿):

<!-- 7000x5000 源,解码后约 140MB -->
<img class="clip" data-start="0" data-duration="3"
src="./assets/hero-scene.jpg" />

修复后(适配画布):

# 批量调整图片大小以适配 3840x3840,保持宽高比
mkdir -p assets/resized
mogrify -path assets/resized -resize 3840x3840\> assets/*.jpg
<!-- 约 3840x2560 源,解码后约 40MB -->
<img class="clip" data-start="0" data-duration="3"
src="./assets/resized/hero-scene.jpg" />

经验法则: 源图片最大为画布尺寸的 2 倍。对于 1920×1080 合成,3840×2160 已经足够。参见性能:图片尺寸

症状: 特定场景在预览中降至 5-10fps。其他场景正常。

原因: 在大元素上使用 backdrop-filter: blur(),尤其是在大半径下堆叠。每个模糊层迫使合成器读取元素后面的像素,运行模糊内核,并合成结果。堆叠层会倍增成本。

修复前(昂贵):

/* 每侧 8 层 = 每帧 16 次模糊处理 */
.pb-1 { backdrop-filter: blur(1px); }
.pb-2 { backdrop-filter: blur(2px); }
.pb-3 { backdrop-filter: blur(4px); }
.pb-4 { backdrop-filter: blur(8px); }
.pb-5 { backdrop-filter: blur(16px); }
.pb-6 { backdrop-filter: blur(32px); }
.pb-7 { backdrop-filter: blur(64px); }
.pb-8 { backdrop-filter: blur(128px); }

修复后(3 个优化层):

/* 更少的处理次数,手动选择的半径——视觉效果相似,成本低得多 */
.pb-1 { backdrop-filter: blur(4px); }
.pb-2 { backdrop-filter: blur(16px); }
.pb-3 { backdrop-filter: blur(48px); }

指南:

  • 每个区域的堆叠 backdrop-filter 层最多 2-3 个
  • 避免在大面积上使用超过 64px 的半径——最大的半径主导总成本
  • 对于静态模糊效果,预渲染为 PNG 然后用普通 <img> 叠加

参见性能:backdrop-filter: blur()了解完整分析。

症状: 期望 HDR 渲染,但输出看起来与 SDR 相同,或 ffprobe 报告 color_transfer=bt709

原因: 默认情况下,Hyperframes 仅在源 <video><img> 标记了 BT.2020 / PQ / HLG 颜色元数据时才切换到 HDR 编码。HDR 未启用的常见原因:

  1. 所有源都是 SDR。 自动检测将仅 SDR 的合成保持为 SDR。使用 ffprobe 验证:
ffprobe -v error -show_streams source.mp4 | grep color_transfer
# 期望:smpte2084 (PQ) 或 arib-std-b67 (HLG)
# SDR:bt709、smpte170m、bt470bg 等
  1. 输出格式错误。 HDR 输出需要 MP4。--format mov--format webm 会回退到 SDR——Hyperframes 会在发生这种情况时记录警告。
  2. 强制使用了 SDR。 --sdr 即使存在 HDR 源也会禁用 HDR。

如果你需要无论源元数据如何都使用 HDR,请使用 --hdr 强制启用。

--docker 的工作方式与本地渲染相同——自动检测、--hdr--sdr 都会转发到容器中并产生相同的输出决策(较慢,因为容器回退到软件 WebGL 进行 SDR DOM 捕获)。

参见 HDR 渲染了解完整的源要求和验证步骤。

症状: 动画不播放。合成看起来是静态的。

原因: window.__timelines 中使用的键必须与合成根元素上的 data-composition-id 属性完全匹配。

修复前(有问题):

// 不匹配:HTML 说 "my-video",脚本注册 "root"
// <div data-composition-id="my-video" ...>
window.__timelines["root"] = tl;

修复后:

// 键与 data-composition-id 属性匹配
// <div data-composition-id="my-video" ...>
window.__timelines["my-video"] = tl;

调试检查清单

当某些内容不工作时,按此顺序检查:

  1. 运行 lint: npx hyperframes lint — 捕获大多数结构性问题
  2. 时间线已注册? window.__timelines["<id>"] 是否设置?键是否匹配 data-composition-id
  3. 仅 GSAP 动画? 只对视觉属性(opacity、transform、color)做动画——参见 GSAP 动画
  4. 时间线够长? 在末尾添加 tl.set({}, {}, DURATION)——参见时间线时长
  5. 控制台错误? 打开浏览器控制台——运行时错误显示为 [Browser:ERROR]
  6. 仍然有问题? 参见故障排除了解环境和渲染问题

下一步