loading="lazy"仅对img和iframe生效,div等标签无效;非img/iframe需用intersectionobserver手动控制,且须设rootmargin提前加载并防内存泄漏。

loading="lazy"只对img和iframe生效,别往div上硬套
浏览器原生loading="lazy"根本不管div、section或自定义组件——它只识别img和iframe标签,且必须带src属性。你给一个<div>加<code>loading="lazy",浏览器直接忽略,连解析都不会进懒加载逻辑。
常见错误现象:
– 页面滚动时,预期“懒加载”的区块仍全部渲染,内存暴涨、滚动卡顿
– 控制台无报错,但Network里所有资源一次性发出请求
– 用getBoundingClientRect()测发现元素早就在视口内,但loading="lazy"没触发(因为它本来就不支持)
-
img和iframe以外的结构,必须用IntersectionObserver或滚动监听手动控制 - 首屏关键区块(如导航栏、主Banner)绝不能依赖
loading="lazy",它不生效,还可能干扰SSR hydration - 若用框架(React/Vue),
loading="lazy"对组件内嵌的img仍有效,但组件自身渲染时机得靠useEffect/onMounted+IntersectionObserver
IntersectionObserver要配rootMargin,否则滚动快时白屏
不设rootMargin的IntersectionObserver默认等元素真正进入视口才触发,用户快速滚动时,图片/区块刚露头才开始加载,视觉上就是“一帧空白”。这不是bug,是默认行为。
典型参数建议:
– 提前加载:设rootMargin: "0px 0px 200px 0px",让元素距离视口底部200px时就触发
– 防抖控制:每个目标只观察一次,回调里必须立刻调用observer.unobserve(target),否则重复执行、内存泄漏
– 动态插入内容(如AJAX返回的HTML片段)需在innerHTML赋值后,显式对新img调用observer.observe()
- 不要用
scroll事件替代——它在主线程频繁触发,长页面下极易掉帧 - 老浏览器(Safari getBoundingClientRect() + 节流,但性能差很多
-
threshold设0.01比0更稳妥,避免因小数精度导致isIntersecting始终为false
display: none不是懒加载,只是藏起来
把12个section全写进HTML,再用display: none隐藏9个,浏览器依然要解析、构建DOM、计算样式——只是不绘制而已。内存占用、JS选择器遍历、事件监听开销全在,滚动卡顿照旧。
真正有效的做法是:初始DOM只保留首屏区块,其余区块用fetch()按需拉取HTML字符串,再insertAdjacentHTML('beforeend', htmlString)注入。服务端返回的必须是纯片段(不含、),且<script></script>标签不会自动执行。
- 注入后若需运行脚本,得用
DOMParser解析HTML,遍历script标签,动态创建并appendChild到document - 用
data-section="a"这类属性做标识,配合IntersectionObserver触发fetch,而非监听scroll事件 - 首次加载完的区块,后续滚动回退时应保留在DOM中(避免重复请求),但可对离屏过远的区块调用
removeChild()释放内存
iframe的loading="lazy"基本无效,首屏必须手动控制
规范强制:只要iframe初始位置在视口内(getBoundingClientRect().top ),无论有没有<code>loading="lazy",浏览器都会立即请求并阻塞DOM解析。你在DevTools里看到的请求时间,一定早于DOMContentLoaded。
能真正延迟的场景极少:
– iframe静态写在HTML末尾(offsetTop > 2 * window.innerHeight)
– src是确定地址(不能含随机参数或重定向)
– 响应头带Cache-Control: public, max-age=3600
– 父容器没设overflow: hidden或transform
- 首屏必须显示的
iframe(如地图、登录弹窗),删掉src,改用data-src,靠IntersectionObserver触发赋值 - 赋值后务必监听
iframe.onload,但iframe.contentWindow访问受同源策略限制,不能直接读document - 跨域
iframe加载失败时,naturalWidth始终为0,只能靠超时判断+fallback占位
实际项目里最易被忽略的点:懒加载不是“加个属性就完事”,而是资源生命周期管理。从DOM存在与否、JS执行时机、缓存策略到错误兜底,每一步都得闭环。尤其当多个懒加载逻辑叠加(比如图片+区块+iframe),observer实例、fetch请求、DOM清理必须一一对应,漏掉一个unobserve()或removeChild(),内存就悄悄涨上去。











