前端懒加载实战:提升页面性能的关键技巧

保霞的笔记 优化 阅读 639
赞 19 收藏
二维码
手机扫码查看
反馈

核心代码就这几行,但别急着复制

我写过好几个项目都用到懒加载,最开始是自己手搓 Intersection Observer,后来发现直接用现成的库更省事。但不管用哪种方式,核心逻辑其实就那几行。先看一个最基础的实现:

前端懒加载实战:提升页面性能的关键技巧

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      observer.unobserve(img);
    }
  });
});

document.querySelectorAll('img[data-src]').forEach(img => {
  observer.observe(img);
});

配合 HTML 这样写:

<img data-src="https://jztheme.com/images/photo1.jpg" alt="示例图" loading="lazy">

注意,这里我加了 loading="lazy" 是为了双重保险(后面会讲为啥)。这个方案亲测有效,兼容性也还行(Chrome 51+、Firefox 55+ 都支持),但如果你要兼容老浏览器,得自己 polyfill 或者换方案。

这个场景最好用:长列表图片 + 视频封面

我最近做的一个内容型页面,一屏有 20+ 张图,首屏加载慢得像蜗牛。用了懒加载之后,FCP(First Contentful Paint)直接从 3.2s 降到 1.1s,Lighthouse 分数涨了 20 多分。特别是那种带视频缩略图的,用户没滚动到的地方根本不用加载,省流量又快。

但要注意一点:**别对首屏元素做懒加载**。我之前图省事,把所有 img 都加了 data-src,结果首屏图片也延迟加载,白屏时间反而变长了。后来改成了只对非首屏的图片加懒加载,或者用 CSS 判断是否在视口内再决定是否触发。

另外,如果你用的是 React/Vue,建议封装成组件。比如我在 Vue 里写了个 <LazyImage> 组件,内部用 onMounted 挂载 observer,卸载时记得 unobserve,不然内存泄漏等着你。

踩坑提醒:这三点一定注意

第一,别忘了 unobserve。很多人只写了 observe,没写 unobserve,结果图片加载完后还在监听,性能白白浪费。上面那段代码里我加了 observer.unobserve(img),就是干这个的。

第二,placeholder 别空着。如果你的 img 没设置宽高,或者没给占位背景,图片加载前会“闪一下”,布局会跳动。我一般会给个灰色背景,或者用 SVG 占位符。比如:

.lazy-img {
  background: #f0f0f0;
  width: 100%;
  height: 200px;
  object-fit: cover;
}

第三,别和 CSS 动画/transform 混用出问题。有一次我给图片加了 transform: scale(0.95),结果 Intersection Observer 的检测区域偏移了,图片滚到一半才加载。后来查文档才知道,如果父元素用了 transform,会创建新的包含块(containing block),影响检测。解决办法是把 observer 挂在没 transform 的祖先节点上,或者用 rootMargin 补偿。

高级技巧:提前加载 + 自定义阈值

有时候用户滚得快,图片还没加载完就看到了空白。这时候可以用 rootMargin 提前触发。比如我想让图片在进入视口前 200px 就开始加载:

const observer = new IntersectionObserver((entries) => {
  // ...同上
}, {
  rootMargin: '200px 0px'
});

这样体验更流畅。我试过 100px200px,发现 200px 在大多数场景下够用,又不会太早加载浪费带宽。

还有个骚操作:结合 requestIdleCallback 延迟加载非关键资源。比如有些装饰性小图标,可以等主线程空闲再加载。不过这个我用得少,因为兼容性差,而且收益不大,除非你的页面特别重。

要不要用现成的库?

说实话,现在我基本直接用 lozad.js 或者 vanilla-lazyload。自己写虽然灵活,但边界情况太多,比如 SSR、动态插入 DOM、响应式图片(srcset)这些,库都处理好了。

比如用 vanilla-lazyload,几行就搞定:

import LazyLoad from "vanilla-lazyload";
const lazyLoadInstance = new LazyLoad({
  elements_selector: ".lazy",
  threshold: 200
});

然后 HTML 里:

<img class="lazy" data-src="https://jztheme.com/images/photo2.jpg" />

它还支持背景图、iframe、视频,甚至能自动处理 srcset。我折腾过自己实现 srcset,结果各种设备像素比搞晕了,不如直接用库省心。

但如果你项目很小,或者不想引入额外依赖,手写 Intersection Observer 也完全够用。我就有个小活动页,只有 5 张图,手写 10 行代码搞定,没必要上库。

最后说点实在的

懒加载不是万能的。如果你的图片本身很小(比如 icon),或者数量不多(比如首页 banner),加了反而增加复杂度。我见过有人连 logo 都懒加载,结果首屏渲染更慢,纯属本末倒置。

另外,现在 Chrome 已经原生支持 loading="lazy",对于简单场景,直接加这个属性就行,不用写 JS。但它的触发时机比较保守(通常在视口边缘才加载),而且不支持自定义阈值,所以复杂场景还是得自己控制。

以上是我踩坑后的总结,希望对你有帮助。这个技术的拓展用法还有很多,比如结合 WebP 格式、按网络状态切换加载策略,后续会继续分享这类博客。有更优的实现方式欢迎评论区交流,一起少走弯路。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论