预加载需主动感知用户行为:用rootmargin提前触发请求,weakmap+map双层去重,结合滚动趋势动态调整阈值,并在解码完成后再赋值src。

核心是把“预加载”从被动响应升级为主动感知——不等元素进视口,而是在它大概率即将出现前就发起请求。关键不在监听是否可见,而在判断“何时该准备”。
用 rootMargin 实现时间维度的提前触发
threshold 只管比例,rootMargin 才管时机。设 rootMargin: "200px",相当于把视口向下延伸 200px,元素离真正可见还有约 200px 距离时就触发回调。这 200–400ms 的窗口,足够完成 fetch、解码、缓存等操作。
- 桌面端滚动慢,常用 "300px"
- 中速滚动的手机建议 "150px"
- 三折叠设备(如 Mate XT)需动态适配:`${window.innerHeight * 0.3}px`,避免因尺寸突变导致预取失效
用 WeakMap + Map 做双层去重控制
模块化场景下,同一张图可能被多个组件引用,同一模块也可能被多次 observe。不加约束会引发重复请求、带宽浪费甚至 CDN 限流。
- 用 WeakMap 缓存每个 DOM 元素的状态:key 是 element,value 是
{ url, promise, timestamp };预取前先查,promise 存在且未 reject 就直接复用 - 用 Map 对全局 URL 去重:key 是
dataset.src,value 是对应 fetch Promise,确保相同资源只请求一次 - 内部限制并发数,例如用
Promise.allSettled(queue.slice(0, 3))控制最多同时预取 3 个资源
结合滚动趋势做轻量预测
“感知式”的本质,是让预取节奏贴合用户行为意图,而不是死守像素阈值。
- 对 scroll 事件做节流采样(仅用于趋势判断),每 100ms 记一次
scrollY,算最近 300ms 的deltaY符号和幅度 - 若连续两次
deltaY > 0(向下滚动),自动把 rootMargin 加大到 "400px";向上滚动时收窄为 "100px",减少误触发 - 对轮播图、放大镜等交互密集模块,监听
mouseenter,立即触发其下所有data-prefetch图片的预取,跳过 observer 状态判断
确保资源真正可用再赋值
预取 ≠ 预加载完成。网络拿到数据后,图片还需解码才能渲染。直接赋值 src 可能导致闪动或卡顿。
- fetch 到 blob 后,创建
URL.createObjectURL(blob) - 用
img.decode()主动触发解码,Promise resolve 后再赋值img.src - 解码失败时回退到原逻辑,并标记该资源本次预取无效,避免重复尝试










