懒加载如何实现?

访客 性能优化 2

懒加载如何实现?从原理到实战的完整指南

📖 目录导读

  1. 什么是懒加载?为什么需要它?
  2. 懒加载的核心原理
  3. 三种主流实现方式详解
  4. 实战代码:从零构建懒加载
  5. 性能优化与最佳实践
  6. 常见问题问答(FAQ)

什么是懒加载?为什么需要它?

懒加载(Lazy Load) 是一种延迟加载技术,仅在资源(图片、视频、脚本等)进入用户可视区域时才加载,而非页面初始化时全部加载,它的核心价值在于:

  • 提升首屏加载速度:减少初始HTTP请求和带宽占用
  • 节省流量:移动端用户无需加载未看到的资源
  • 改善LCP指标:Google Core Web Vitals 中,懒加载可显著提升 Largest Contentful Paint

适用场景:长页面、瀑布流、电商列表、新闻资讯等包含大量图片或媒体的页面。


懒加载的核心原理

懒加载的实现基于判断元素是否进入视口(viewport),其逻辑闭环如下:

用户滚动页面 → 检测元素与视口的位置关系 → 若进入视口 → 触发资源加载(如替换data-src为src) → 移除监听

两种主流检测方案:

方案 原理 兼容性 性能
getBoundingClientRect() 计算元素位置与视口距离 全兼容(IE9+) 需绑定scroll事件,频繁计算
Intersection Observer 浏览器原生API异步监听 现代浏览器(IE不支持) 性能极优,无主线程阻塞

三种主流实现方式详解

纯JavaScript(getBoundingClientRect)

// 传统scroll监听方案
const images = document.querySelectorAll('[data-src]');
function checkVisible(el) {
  const rect = el.getBoundingClientRect();
  return rect.top < window.innerHeight && rect.bottom > 0;
}
function lazyLoad() {
  images.forEach(img => {
    if (checkVisible(img) && img.dataset.src) {
      img.src = img.dataset.src;
      img.removeAttribute('data-src');
    }
  });
  // 优化:若所有图片已加载,移除监听
  if (document.querySelectorAll('[data-src]').length === 0) {
    window.removeEventListener('scroll', lazyLoad);
  }
}
// 节流优化
window.addEventListener('scroll', throttle(lazyLoad, 200));
window.addEventListener('resize', throttle(lazyLoad, 200));

缺点:频繁触发scroll事件,需配合throttle/debounce,仍有可能造成主线程阻塞。

Intersection Observer(推荐)

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      img.removeAttribute('data-src');
      observer.unobserve(img);  // 加载后取消观察
    }
  });
}, {
  rootMargin: '0px 0px 200px 0px',  // 提前200px预加载
  threshold: 0.01
});
document.querySelectorAll('[data-src]').forEach(img => observer.observe(img));

优势
✅ 异步执行,不阻塞主线程
✅ 自动处理滚动、缩放场景
✅ 支持rootMargin提前预加载(提升用户体验)
✅ 兼容Chrome 51+, Firefox 55+, Safari 12.1+

HTML原生loading属性(最简方案)

<img src="placeholder.jpg" data-src="real-image.jpg" loading="lazy" alt="示例">
<script>
// 注意:仅支持Chrome 76+,Firefox 75+,Safari 15.4+
// 结合data-src作为降级方案
</script>

注意:原生loading="lazy"无需JS,但无法自定义预加载距离,且对<iframe>支持有限。


实战代码:从零构建懒加载

基础HTML结构(使用data-*属性)

<img class="lazy" data-src="https://example.com/high-res.jpg" 
     src="/placeholder.png" 
     alt="产品图" 
     width="800" height="600">

核心JavaScript(双重降级策略)

( function() {
  const lazyImages = document.querySelectorAll('.lazy[data-src]');
  // 检测浏览器是否支持Intersection Observer
  if ('IntersectionObserver' in window) {
    const observer = new IntersectionObserver((entries, obs) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          const img = entry.target;
          img.src = img.dataset.src;
          img.onload = () => img.classList.add('loaded');
          img.removeAttribute('data-src');
          obs.unobserve(img);
        }
      });
    }, { rootMargin: '100px' });
    lazyImages.forEach(img => observer.observe(img));
  } else {
    // 降级方案:传统scroll + 节流
    const lazyLoad = () => {
      lazyImages.forEach(img => {
        if (img.getBoundingClientRect().top < window.innerHeight + 100 && img.dataset.src) {
          img.src = img.dataset.src;
          img.onload = () => img.classList.add('loaded');
          img.removeAttribute('data-src');
        }
      });
    };
    window.addEventListener('scroll', lazyLoad);
    window.addEventListener('resize', lazyLoad);
    lazyLoad(); // 初始检查
  }
})();

CSS占位与过渡

.lazy {
  background: #f0f0f0;  /* 占位背景 */
  transition: opacity 0.3s;
  opacity: 0.5;
}
.lazy.loaded {
  opacity: 1;
}

性能优化与最佳实践

✅ 5.1 自定预加载距离

{ rootMargin: '200px 0px' }  // 提前200px加载,适合高网速环境

根据设备性能调整:低端设备可设为50px,高端设备可设为500px。

✅ 5.2 结合图片尺寸占位

<img data-src="image.jpg" width="800" height="600" />

提前定义宽高,防止懒加载导致布局偏移(CLS)——这是Google排名的重要指标。

✅ 5.3 图片CDN优化

  • 使用WebP格式(通过<picture>标签或服务端协商)
  • 响应式图片:srcset结合data-src延迟加载不同分辨率
    <img data-src="small.jpg" data-srcset="medium.jpg 768w, large.jpg 1024w" ...>

✅ 5.4 懒加载与SEO友好

  • 搜索引擎会抓取data-src中的URL(Google明确说明支持延迟加载属性)
  • 保持alt属性完整
  • 使用<noscript>提供降级:
    <noscript><img src="real-image.jpg" alt="示例"></noscript>

✅ 5.5 组件化封装

推荐使用成熟库:

  • lazysizes:轻量、支持响应式、自动检测
  • vanilla-lazyload:仅5KB,无依赖

常见问题问答(FAQ)

Q1:懒加载会影响SEO排名吗?

不会,Google明确声明会抓取data-srcdata-srcset中的URL,只要确保alt属性存在,且内容与图片关联,就不会被降权,建议同时保留loading="lazy"属性作为额外信号。

Q2:原生loading="lazy"与Intersection Observer哪个更好?

视场景而定

  • 原生方式:零代码、无需兼容性处理(现代浏览器)
  • Observer:支持自定义预加载距离、可同时处理iframe/背景图、支持IE降级
    建议:优先使用Observer封装通用方案,原生作为降级参考。

Q3:图片大小不一致,如何防止布局抖动?

必须提前定义宽高(如<img width="800" height="600">),如果图片比例不固定,可用aspect-ratioCSS属性配合object-fit

.lazy-wrap { aspect-ratio: 16/9; }
.lazy-wrap img { object-fit: cover; }

Q4:视频和iframe如何实现懒加载?

视频:将<video>src属性转为data-src,监听进入视口后赋值。
iframe:替换srcdata-src,Observer触发后还原:

iframe.src = iframe.dataset.src;  // 注意:iframe重新赋值会重新加载

建议对视频使用preload="none"属性减少初始流量。

Q5:移动端滚动性能如何保证?

  • 使用Intersection Observer(无需滚动监听)
  • 如果必须用scroll事件,节流间隔设为200ms以上
  • 使用requestAnimationFrame代替scroll回调
  • 避免在回调中执行DOM查询(如querySelectorAll),缓存元素列表

标签: loading="lazy"

抱歉,评论功能暂时关闭!