IntersectionObserver 不能直接用于预加载,因其仅反馈交集状态而无意图区分、去重、并发控制和缓存策略,硬套易致重复请求、误触、弱网雪崩。

直接结论:预加载系统不能只靠 IntersectionObserver 监听“是否可见”,必须拆成观察、识别、执行、调控四层,否则容易重复请求、误触非首屏内容、在弱网下雪崩。
为什么 IntersectionObserver 本身不能直接用来做预加载
它只告诉你“元素和视口有没有交集”,但不区分意图:导航链接、图片、JS 模块的预取时机和方式完全不同;也不自带去重、并发控制、缓存策略。硬套 new IntersectionObserver(...) 然后一通 fetch(),上线后大概率出现以下问题:
- 同一张图被多个模块监听,触发 3 次
fetch() - 页脚广告位也被监听,用户还没滚到一半,CDN 就被预取请求打满
- 移动端快速滚动时,
rootMargin: "100px"根本追不上,等真正进入视口才开始 fetch,失去“预”的意义
rootMargin 和 threshold 怎么配才真有用
这两个参数不是调参游戏,而是预加载节奏的物理锚点:
-
rootMargin是唯一能“提前时间”的手段——设为"200px",相当于告诉浏览器:“离视口还有 200px 时就该准备了”;桌面端常用"300px",中速滚动手机建议"150px",三折叠设备(如 Mate XT)需动态算:${window.innerHeight * 0.3}px -
threshold: [0, 0.25, 0.5]不是让你写死数组,而是分阶段响应:0% 时只做 DNS 预解析,25% 时fetch(url, { method: 'HEAD' })建连,50% 时才真正拉资源 - 绝对不要监听
document.body;应限定作用域,例如只 observemain[data-prefetch-scope]或带data-prefetch的容器
如何避免重复预取和资源竞争
真实页面里,一个 data-src 可能被轮播图、列表项、弹窗同时引用;不加控制,同一 URL 会发多次请求:
- 用
WeakMap缓存 DOM 元素状态:const prefetchState = new WeakMap(),key 是img元素,value 是{ url, promise, timestamp } - 全局 URL 去重用
Map:urlToPromiseMap.set(url, promise),后续遇到相同url直接复用 Promise - 并发数必须硬限:内部用
Promise.allSettled(queue.slice(0, 3))控制最多 3 个预取任务并行,防止弱网下请求堆积 - 预取完成后的资源不能直接赋值
img.src;要等img.decode()resolve 后再赋,否则可能闪白或错位
策略模块为什么不能省——它决定系统是否“可感知”
纯像素触发的预取是机械的;“感知式”意味着把行为意图编进逻辑里:
- 监听
scroll节流采样(仅用于趋势判断),若连续两次deltaY > 0(向下滚动),动态把rootMargin加大到"400px";向上滚动则收窄为"100px" - 对
mouseenter敏感区域(如商品卡片),立即触发其下所有data-prefetch图片,无视 observer 当前状态 - 根据
navigator.connection.effectiveType动态开关:值为'2g'时禁用图片预取,只保留link[rel="prefetch"]交由浏览器调度
最易被忽略的是:预取 ≠ 加载完成。从 fetch() 到 img.decode() 再到渲染帧提交,中间有至少两个异步阶段;没等解码就赋值 src,用户看到的还是空白占位符。










