loading="lazy"仅对初始HTML中具备width/height且不在首屏的<img>生效,动态生成、缺失尺寸、父容器有transform等均退化为eager;自定义元素需动态import+define才真正按需加载。

loading="lazy"只对初始 HTML 中的 <img> 生效
写了 loading="lazy" 但图片仍全量加载?不是浏览器不支持,而是它根本没进入懒加载逻辑。浏览器只在首次 layout 后批量检查「初始 HTML 就存在」且「不在首屏」的 <img> 元素。以下任一条件不满足,就会退化为 eager:
-
<img>缺少width和height(设为100%或vw也不行) - 父容器有
transform、overflow: hidden、contain: layout或will-change - 图片由 Vue
v-for、Reactmap()动态生成 —— 不属于初始 HTML -
loading="lazy"错写在<source>上(该属性仅对<img>有效) - 用
position: absolute; top: -9999px占位,浏览器判定为“已渲染但不可见”,跳过懒加载
首屏关键图(如 banner、logo)必须显式写 loading="eager",并配 <link rel="preload" as="image" href="xxx">,且 href 必须和 src 完全一致。
自定义元素(Custom Elements)不能靠 customElements.define() 自动延迟
调用 customElements.define('my-card', MyCard) 只是注册构造函数,不触发实例化,更不等于组件渲染。常见误解是“定义晚一点 = 延迟加载”,其实真正卡点在升级时机和 DOM 连接状态:
- 组件标签(如
<my-card>)在 HTML 中已存在,但未定义 → 浏览器先挂载为 unknown 元素,等定义后才升级;升级发生在 DOMContentLoaded 后首个 microtask 或首次 query 时 - 组件被
display: none包裹但仍连入 document → 会升级,但不可见;若完全未 append 到 document → 不升级,connectedCallback永不触发 - 组件在 Shadow DOM 内部创建,但宿主元素尚未连入文档 → 升级延迟至宿主连入后
- 用了
customElements.whenDefined('my-card').then(...)却没 catch reject → 名称拼错或重复 define 都会静默失败
真正按需加载要两步:动态 import('./my-card.js') 下载代码 + customElements.define() 注册,且绝不能对同一名称重复 define,否则抛 DOMException。
立即学习“前端免费学习笔记(深入)”;
IntersectionObserver 是延迟初始化的底层支撑,但容易内存泄漏
用 IntersectionObserver 监听视口变化,比监听 scroll 更高效,但它不是“开了就完事”。三个硬性约束必须手动处理:
-
threshold建议设为0.01而非0,避免因小数像素计算误差导致元素刚进视口却错过回调 - 每个观察到的元素必须显式调用
observer.unobserve(el),尤其在 React/Vue 列表项频繁销毁重建时,漏掉这步必致内存泄漏 - 服务端渲染(SSR)下,初始 HTML 不能留空容器,得包含骨架屏之类最小可用结构,否则首屏不可见、SEO 归零
如果目标只是“让浏览器跳过渲染和布局”,而不是“延迟请求资源”,content-visibility: auto 更轻量——但需配合 contain-intrinsic-size(如 500px),且 Safari 至今不支持(截至 Safari 17.5)。
v-if 在 Vue 中不是懒加载,而是条件渲染
v-if 控制的是组件是否被创建和挂载,不是“延迟初始化”。当 v-if="false" 时,组件实例根本不会创建,created、mounted 都不执行;一旦变为 true,才走完整生命周期。但它不解决 JS 模块下载问题,也不减少首屏解析压力。
- 适合控制用户交互后才出现的模块(如弹窗、设置页),但不适合“滚动到才加载”的长列表项
- 若组件内含大量模板或样式,建议结合
defineAsyncComponent+import()实现真正代码分割 - 不要依赖
v-if做性能优化主手段——它只是 DOM 开关,不是资源调度器
真正复杂的延迟初始化,往往要组合 IntersectionObserver 触发时机 + 动态 import 加载代码 + customElements.define 注册类 + content-visibility 跳过渲染,四者缺一不可。单点优化容易翻车,比如只加 loading="lazy" 却忽略宽高,或只用 v-if 却没拆分模块代码。



















