懒加载 + 防抖节流:优化网页性能的关键技术

88 阅读9分钟

在现代网页开发中,图片是提升用户体验的重要元素,同时也是影响网页性能的主要瓶颈。随着网页内容的复杂化和图片数量的增加,一次性加载所有图片会导致页面加载速度变慢、服务器压力增大,甚至可能让用户在等待页面加载中流失。

为了解决这一问题,图片懒加载(Lazy Loading)应运而生,本文将结合深入解析图片懒加载的原理,希望看完的能有所收获。


一、什么是图片懒加载?

1.1 概念

图片懒加载是一种延迟加载技术,其核心思想是仅在用户需要查看图片时才加载图片

具体来说就是,当用户滚动页面时,只有进入可视区域(窗口)的图片才会被加载,而未进入可视区域的图片则保持“惰性”状态,直到用户接近它们时才触发加载。

1.2 为什么要使用懒加载?

  • 减少初始加载时间:页面首次加载时,仅加载首屏可见的图片,可以避免大量图片同时请求。
  • 降低服务器压力:减少不必要的图片请求,尤其适用于高并发场景。
  • 节省带宽和流量:用户可能不会滚动到页面底部,未加载的图片不会消耗用户的流量。
  • 提升用户体验:页面加载速度更快,用户能更快看到核心内容。

二、懒加载的实现原理

2.1. 核心逻辑

懒加载的实现依赖于以下三个关键点:

  1. 检测元素是否进入可视区域:通过 getBoundingClientRect() 方法判断图片是否在用户当前视口范围内。
  2. 延迟加载图片:使用 data-original 属性存储真实图片地址,通过 JavaScript 动态赋值给 src 属性。
  3. 事件监听:监听页面的 scroll 事件,实时检测图片是否进入可视区域。

2.2 关键技术点

  • data-original 属性:这是 HTML5 中的自定义数据属性(data-XXX),用于存储真实图片的 URL。在页面加载时,src 属性指向一个占位图或空值,而真实图片地址通过 data-original 保存,待用户滚动到可视区域时再赋值给 src
  • getBoundingClientRect() 方法:该方法返回元素相对于视口的位置信息,包括 topbottomleftright 等属性,可用于判断元素是否进入可视区域。
  • Intersection Observer API:现代浏览器推荐使用的更高效的懒加载实现方式,能够替代传统的 scroll 事件监听。

下面,我将通过一段代码案例,带大家深入了解一下。


三、代码案例详解

3.1 基础代码

<img 
  class="image-item" 
  lazyload="true" 
  src="../../image/placeholder.gif" //占位图
  data-original="https://example.com/real-image.jpg"   //实际上要展示的图片
/>
  • src 属性:指向一个占位图(如低分辨率的缩略图或纯色背景),确保图片容器在页面布局中占位。
  • data-original 属性:存储真实图片的地址,只有在用户滚动到该图片时才会被赋值给 src

3.2 JS 实现

const viewHeight = document.documentElement.clientHeight;
const eles = document.querySelectorAll('img[data-original][lazyload]');

window.lazyload = function () {
  Array.prototype.forEach.call(eles, function (item, index) {
    if (item.dataset.original === "") return;

    let rect = item.getBoundingClientRect();
    if (rect.bottom >= 0 && rect.top <= viewHeight) {
      (function () {
        var img = new Image();
        img.src = item.dataset.original;
        img.onload = function () {
          item.src = item.dataset.original;
          item.removeAttribute('data-original');
          item.removeAttribute('lazyload');
        };
      })();
    }
  });
};

// 监听事件
document.addEventListener('DOMContentLoaded', lazyload);
window.addEventListener('scroll', lazyload);

3.2.1 代码解析

  1. 获取可视区域高度viewHeight 表示用户当前视口的高度。
  2. 选择需要懒加载的图片:通过 querySelectorAll 选择所有具有 data-originallazyload 属性的 <img> 元素。
  3. 遍历图片并检测是否进入可视区域
    • 使用 getBoundingClientRect() 获取图片的位置信息。
    • 如果图片的 bottom 位置大于等于 0,且 top 位置小于等于视口高度,则认为图片进入可视区域。
  4. 加载真实图片
    • 创建内存中的 Image 对象,预加载真实图片。
    • 使用 onload 事件确保图片加载完成后,将其赋值给 src 属性。
    • 移除 data-originallazyload 属性,避免重复加载。

3.2.2 注意事项

  • 闭包的使用:代码中使用闭包包裹加载逻辑,确保每次加载的图片是独立的。
  • 移除属性:加载完成后移除 data-originallazyload 属性,避免后续的 scroll 事件重复触发加载。
  • 事件监听:在页面加载完成后(DOMContentLoaded)和用户滚动时(scroll)触发懒加载逻辑。

四、优化策略

4.1 占位图的选择

在图片懒加载中,占位图的选择直接影响页面的初始加载性能和用户体验,以下为两种常见策略:

1. 低分辨率缩略图

  • 原理:使用小尺寸、低质量的缩略图作为 src 初始值,可以减少首次加载的资源体积。
  • 实现方式
    <img src="low-res-image.jpg" data-original="high-res-image.jpg" />
    
  • 优势
    • 显著降低初始页面加载时间,尤其适合图片密集型页面(如电商商品列表)。
    • 用户能立即看到占位图,避免空白区域导致的视觉不适。
  • 注意事项
    • 缩略图需保持与目标图片的宽高比一致,防止布局抖动。
    • 若缩略图过大(如超过 50KB),可能抵消优化效果。

2. 纯色背景占位图

  • 原理:使用与页面背景一致的纯色(如白色、灰色)填充图片容器,模拟真实图片的布局。
  • 实现方式
    <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" 
         data-original="high-res-image.jpg" 
         style="background-color: #000;" />
    
  • 优势
    • 零字节请求,完全避免额外网络开销。
    • 适用于所有屏幕尺寸,无需适配不同分辨率。
  • 典型案例
    • 京东、淘宝等电商平台采用纯色占位图,确保页面首屏快速渲染。

4.2 防抖与节流

在传统懒加载实现中,频繁触发 scroll 事件会导致性能问题,通过 节流(Throttle)防抖(Debounce) 技术可以显著优化。

1. 节流(Throttle)

  • 原理:限制 scroll 事件的触发频率,确保单位时间内只执行一次回调。
  • 实现代码
    let ticking = false;
    window.addEventListener('scroll', () => {
      if (!ticking) {
        window.requestAnimationFrame(() => {
          lazyload(); // 执行懒加载逻辑
          ticking = false;
        });
        ticking = true;
      }
    });
    
  • 优势
    • 利用 requestAnimationFrame 与浏览器刷新率同步,减少无效计算。
    • setTimeout 更精确,避免卡顿感。

2. 防抖(Debounce)

  • 适用场景:用户快速滚动后需要延迟执行懒加载(如无限滚动)。
  • 实现代码
    let debounceTimer;
    window.addEventListener('scroll', () => {
      clearTimeout(debounceTimer);
      debounceTimer = setTimeout(() => {
        lazyload();
      }, 200); // 200ms 内无滚动则执行
    });
    
  • 注意事项
    • 防抖可能导致部分图片加载延迟,需权衡用户体验与性能。

4.3 Intersection Observer API

Intersection Observer API 是现代懒加载的标准解决方案,替代了传统的 scroll + getBoundingClientRect() 方法。

1. 核心优势

  • 异步非阻塞:由浏览器底层异步检测元素可见性,不依赖 scroll 事件。
  • 性能优化
    • 避免频繁调用 getBoundingClientRect() 触发回流(Reflow)。
    • 减少 JavaScript 执行频率,降低 CPU 占用。

2. 实现代码

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.original;
      img.removeAttribute('data-original');
      img.removeAttribute('lazyload');
      observer.unobserve(img); // 停止观察已加载的图片
    }
  });
});

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

3. 参数详解

  • threshold:设置触发回调的交集比例(如 0.1 表示元素 10% 进入视口时触发)。
  • rootMargin:扩展/缩小检测区域(如 "0px 0px 200px 0px" 表示提前 200px 加载图片)。

4. 与传统方案对比

特性传统方案(scroll + getBoundingClientRect)Intersection Observer API
回流触发高频触发,性能差无回流
代码复杂度复杂,需手动管理事件简洁
兼容性全面支持需 Polyfill 支持 IE
动态内容适配需手动重新监听自动适配新增元素

4.4 响应式图片

通过 srcsetsizes 属性,可以根据设备分辨率动态加载不同尺寸的图片,进一步优化带宽和加载速度。

1. srcsetsizes 的协同工作

  • srcset:提供多组图片路径及对应的宽度描述符(如 1920w)。
  • sizes:定义不同屏幕宽度下的图片显示尺寸(如 100vw 表示视口宽度)。
  • 实现代码
    <img 
      src="low-res-image.jpg" 
      srcset="high-res-image.jpg 1920w, low-res-image.jpg 1024w" 
      sizes="(min-width: 1200px) 1920px, 100vw"
      alt="Responsive Image"
    />
    
  • 浏览器行为
    1. 浏览器根据当前视口宽度匹配 sizes 中的条件。
    2. srcset 中选择最合适的图片加载(优先高分辨率设备)。

2. 与懒加载结合的优化

  • 动态加载响应式图片
    在懒加载中,将 data-original 替换为完整的 srcsetsizes
    <img 
      src="low-res-image.jpg" 
      data-srcset="high-res-image.jpg 1920w, low-res-image.jpg 1024w" 
      data-sizes="(min-width: 1200px) 1920px, 100vw"
      lazyload="true"
    />
    
    IntersectionObserver 回调中动态赋值:
    img.srcset = img.dataset.srcset;
    img.sizes = img.dataset.sizes;
    

3. 优势与注意事项

  • 优势
    • 避免大图在小屏设备上浪费带宽。
    • 提升加载速度和用户体验(如移动端首屏秒开)。
  • 注意事项
    • 确保 srcset 中的图片已通过 CDN 优化。
    • 使用 picture 标签支持 WebP 等现代格式:
      <picture>
        <source srcset="image.webp" type="image/webp">
        <img src="image.jpg" alt="WebP Support">
      </picture>
      

4.5 综合优化建议

  1. 组合策略
    • 占位图 + Intersection Observer + srcset 可覆盖性能、兼容性和响应式需求。
  2. 动态内容处理
    • 对于无限滚动或动态加载的内容,需在新增元素后重新绑定 IntersectionObserver
  3. 工具辅助
    • 使用 Lighthouse 工具分析图片加载性能,优化加载优先级。

通过以上策略,可显著提升网页的加载性能,同时兼顾用户体验和开发效率。


五、技术的实际应用场景

5.1 电商网站

  • 商品列表页:用户滚动页面时,商品图片逐行加载,避免一次性加载所有商品图片。
  • 详情页:图片轮播图和商品详情图使用懒加载,提升页面打开速度。

5.2 新闻门户

  • 文章列表:长列表中的图片懒加载,减少首屏加载时间。
  • 图片新闻:多张图片的新闻页面,用户滚动时逐步加载图片。

5.3 社交媒体

  • 动态流:用户滚动时加载动态流中的图片和视频,避免页面卡顿。

六、总结

图片懒加载是提升网页性能的重要手段,其核心在于按需加载延迟加载,通过合理使用 data-original 属性、getBoundingClientRect() 方法以及现代的 Intersection Observer API,开发者可以显著优化页面加载速度和用户体验。


本文均为作者个人理解,如果发现内容有误,欢迎各位读者在评论区指正。

最后,创作不易,要是觉得这篇文章对你有所帮助,不妨动动小手,点赞 + 收藏 !🌟