预扫描器仅线性扫描首层HTML字节流,深度超6层、惰性容器内、无src属性、CSS背景图、JS动态设置及head中靠后或被阻塞的preload均失效。
<img>
浏览器的预加载扫描器(Preload Scanner)只做一次线性字节流扫描,不构建 DOM,也不递归解析嵌套。它在 DOM 深度达到 6 层时主动截断——这意味着 <div><div><div><div><div><div><img src="hero.jpg"></div></div></div></div></div></div> 中的 <img> 根本不会被识别为关键资源。
常见错误现象包括:LCP 候选元素始终不发起请求、DevTools Network 面板中该图片的 Priority 显示为 “Medium” 或 “Low”,Initiated By 是 “Other”。
<picture> 内部的 <source> 能被识别,但若整个 <picture> 被包在 <div class="lazy-wrapper"> 里,且该 wrapper 没有 src 或 loading="eager",预扫描器直接跳过<details>、<dialog>、<slot> 这类惰性容器中的资源,几乎 100% 被忽略v-if、*ngIf 或 {isLoaded && <img>} 渲染的图片,在初始 HTML 字节流里压根不存在,预扫描器无从响应<main>、<section> 等语义标签内,能提升其子元素资源的探测可信度;连续多层 <div> 则被当作“结构噪声”快速滑过<link rel="preload"> 必须出现在 <head> 开头附近预扫描器只读取首层 HTML 字节流,一旦遇到阻塞脚本(没加 defer 或 async 的 <script>),就立刻终止扫描。所以 <link rel="preload" as="image" href="/assets/hero.webp"> 如果写在 <script src="vendor.js"></script> 后面,大概率被漏掉。
典型失效场景:
立即学习“前端免费学习笔记(深入)”;
<script> 放在 <head> 最前面,后面所有 <link rel="preload"> 和首屏 <img> 全部失效<script> 体积超过 ~1KB(比如初始化数据过大),会卡住预扫描<link rel="preload"> 被注释、<meta> 或其他无关标签隔开,物理位置离 <head> 起始越远,被扫描到的概率越低as="image" 缺失 → 浏览器当普通 fetch 处理,不启用图片缓存策略,Priority 降级<img src> 是唯一能被预扫描器识别的图片加载属性预扫描器只认静态 src 属性,对 srcset、sizes、data-src、loading 完全无视。哪怕你写了完整的响应式配置,只要没写 src,这张图就不会被提前发现。
使用场景判断:
src(哪怕只是 src="/1x1.gif" 占位),否则无法触发预加载<picture> 包裹时,预扫描器只看最外层 <img src>,不解析内部 <source> —— 所以 fallback src 不可省略loading="eager" 不等于预加载,它只是禁用懒加载;decoding="async" 只影响解码时机,和下载无关img.src(如 el.setAttribute('src', url) 或框架 v-bind:src)在预扫描阶段尚未执行,等同于“不存在”url() 对预扫描器完全无效预扫描器不解析任何 CSS 内容,@import、background-image: url(...)、@font-face { src: url(...) }、content: url(...) 全部被跳过。哪怕路径是绝对 URL、没变量、没 JS 拼接,也不会触发提前请求。
后果很直接:
background-image 实现的首屏大图,LCP 候选元素无法被识别<style>.logo { background: url("logo.png"); }</style> 是最常见也最无效的写法真正可控的替代方案只有两种:<link rel="preload" as="image" href="..."> 或 <link rel="preload" as="font" href="...">,且必须直出在初始 HTML 的 <head> 顶部附近。