触发与降级
前三讲把一张 Live Photo 送进了浏览器并消掉了接缝。最后一层是什么动作让它动起来,以及在不该动的时候如何不动。
1 · 意图与事件的区别
原生相册的交互是长按。桌面 web 上没有长按的习惯,通行做法是 hover。两者都不能直接绑事件了事:
pointerenter一到就播,会让指针扫过列表的过程变成一串乱闪的视频,并顺带触发一批不必要的下载pointerdown一到就播,会和滚动打架:手指按下去是为了滑动页面,不是为了看这张照片动
两者需要的都是意图确认,判据不同:hover 看停留时长,长按看按住时长加上位移是否超阈值。
| 触发 | 确认条件 | 取消条件 |
|---|---|---|
| hover | 停留超过约 140 ms | 指针离开 |
| 长按 | 按住超过约 350 ms | 松手过早,或位移超过约 10 px |
位移那一条是长按判定里最容易漏的。少了它,任何一次以图片为起点的滑动都会在手指离开前先播一段。
src 直到意图确认才挂上,日志里「取源」一步每张只出现一次。可切到长按模式后按下并拖动,观察位移超阈值时的取消;也可开 reduced-motion 看自动播放整条让出。2 · 视频什么时候进 DOM
一个列表里有二十张 Live Photo,若二十段视频都在初始 HTML 里,代价是二十个额外请求与相应的解码器实例。它们当中大多数永远不会被播放。
preload="none" 只拦下预加载,元素本身仍然存在。更彻底的做法是让 src 在意图确认后才挂上——在那之前 DOM 里只有静图,视频元素是个空壳。图 1-1 的日志显示「取源」这一步只在确认之后出现,且每张卡片只出现一次。
这条顺序还有一个副作用值得留意:<video> 同样是 LCP 候选元素(取 poster 载入与首帧呈现两者更早的一个)。src 不挂上,就没有可被计入的 poster 与首帧,LCP 归属稳定落在静图上,这部分带宽也整个移出了首屏预算。
3 · 不该动的时候
prefers-reduced-motion: reduce 是一条明确的用户声明,自动播放应当整条让出,而不是缩短时长或降低帧率。让出之后功能不能消失,得留一个显式入口:一个按钮,或者把触发方式改为点击。
const calm = matchMedia('(prefers-reduced-motion: reduce)');
if (calm.matches) {
showPlayButton(); // 显式入口, 用户点了就播
} else {
bindHoverIntent(); // 自动播放
}
calm.addEventListener('change', rebind); // 偏好可在会话中途改变
用户主动点击那个按钮时,不必再受偏好压制——偏好约束的是「未经请求就动」,不是「用户要求也不给动」。图 1-1 的点击模式即按这条处理。
警示 · 长按在移动端会撞上系统手势。iOS 上长按图片默认弹出分享菜单并高亮选区,需要 -webkit-touch-callout: none 与 user-select: none 一并压掉;touch-action 要留出纵向滚动(本系列用 pan-y),否则页面在图片上滑不动。这些声明写在
_shared.css 的 .stage.pressable 里。
4 · 同时只播一个
多张卡片并存时,指针从一张移到下一张的过程中会短暂地有两个视频在播。
本页原先给这条约束写的理由是「解码器实例有限,放开了播会撞上限,表现为 play() 静默失败」。实测没有支持这个说法:在 macOS 的 HeadlessChrome 151 上逐个加入并播放同一段 H.264,加到 40 路时全部仍在播,play() 没有一次被拒(核对于
2026-08)。上限存在,但在这个量级上碰不到,而一个列表里同屏可见的卡片远少于 40。
于是这条约束的理由改成了行为层面的:新的一张开始前停掉上一张,观感上贴近相册,也避免多段音频叠加(本系列的素材静音,有声场景下这一条是硬要求)。图 1-1 的日志里这一步记作「被另一张接管」。
注 · 上述测量在桌面完成,未在移动设备上复现。移动端的并发解码限制通常紧得多,且与芯片型号相关;把「同时只播一个」当作跨平台的默认约束仍然稳妥,只是不必用解码器上限来解释它。
5 · 一份可用的判断顺序
把四讲的结论合起来,投放一张 Live Photo 的决策链是:
- 静图转成 JPEG / WebP / AVIF——HEIC 在 Chrome 里没有解码器,这一步没有余地(见解得开哪一半 §4)
- 视频转成 H.264 MP4——容器与 HEVC 多半原样可用,转码买的是确定性,不是可用性
- 首屏只放静图,视频元素留空壳
- 意图确认后挂
src,等requestVideoFrameCallback首帧再交叉淡入(见静图与视频之间 §2) prefers-reduced-motion下改为显式入口- 播完退回静图,同时只播一个
其中第 2 步是唯一一条「明知非必需仍然做」的,理由在解得开哪一半 §5。
以上六步用的都是合成素材。换成相册里真正导出的文件,配对、拆包与解码探测各有各的意外,见 真文件的配对与逐张播放。