带你快速了解关于图片懒加载

153 阅读4分钟

一、什么是图片懒加载

让我们问问豆包,它是这么回答的:

微信截图_20250629161432.png

一言蔽之,图片懒加载是一种网页性能优化技术,核心目的是减少页面初始加载时的资源请求量,让图片在用户真正需要看到它时才进行加载,从而提升页面加载速度和用户体验。

二、为什么需要图片懒加载

所以为什么需要图片懒加载呢?让我们将继续追问豆包:

微信截图_20250629163331.png

相较于传统加载的一次性端上所有菜,懒加载则是吃一道上一道,这样的好处是你不用等所有图片都加载完,能更快看到内容,还不浪费流量(没看到的图片不提前加载)。

三、如何实现图片懒加载

首先我们需要解决图片提前加载的矛盾,因为如果直接在<img src>中写真实图片地址,浏览器会立即发起请求,违背懒加载 “按需加载” 的原则。

这该如何解决呢?我们可以用自定义属性(如 data-original)  存储真实图片 URL,src会先赋值为占位符(小尺寸透明图或低清图) ,仅在图片进入可视区域时才将data-original的值赋给src

    <img
      class="image-item"
      lazyload="true"
      src="预加载图片地址"
      data-original="真实图片地址"
    />

这样当页面打开时,会先显示占位图(不影响布局),当用户滚动到该商品时,JS触发加载真实商品图,提升浏览流畅度。

而懒加载的核心逻辑是:先不加载所有图片,等图片出现在浏览器可视区域时,再偷偷加载它

要实现图片懒加载需要判断<img>标签是否出现在可视区域,还需要考虑浏览器窗口的大小以及页面的滚动区域。

这里我们可以用getBoundingClientRect ()方法

这个方法是判断元素是否在可视区域的关键,它返回一个 DOMRect 对象,包含元素相对于浏览器视口的位置信息。

const rect = element.getBoundingClientRect();
// 返回的rect对象包含:
{
  top: 100,     // 元素顶部到视口顶部的距离(px)
  bottom: 300,  // 元素底部到视口顶部的距离(px)
  left: 50,     // 元素左侧到视口左侧的距离(px)
  right: 250,   // 元素右侧到视口左侧的距离(px)
  width: 200,   // 元素宽度(px)
  height: 200,  // 元素高度(px)
  x: 50,        // 等同于left
  y: 100        // 等同于top
}

设置核心判断条件:元素顶部低于视口顶部,且元素底部高于视口底部

// 获取视口高度
const viewportHeight = window.innerHeight;

// 判断元素是否在视口内
function isInViewport(element) {
  const rect = element.getBoundingClientRect();
  return (
    rect.top <= viewportHeight &&  // 元素顶部在视口内或视口上方
    rect.bottom >= 0              // 元素底部在视口内或视口下方
  );
}

整体代码实现如下

// 1. 获取视口高度
const viewportHeight = window.innerHeight;

// 2. 获取所有需要懒加载的图片(通过自定义属性标记)
const lazyImages = document.querySelectorAll('img[data-src]');

// 3. 定义可视区域判断函数
function isInViewport(element) {
  const rect = element.getBoundingClientRect();
  // 核心判断条件:元素顶部低于视口底部,且元素底部高于视口顶部
  return (
    rect.top <= viewportHeight &&  // 元素顶部在视口内或视口上方
    rect.bottom >= 0              // 元素底部在视口内或视口下方
  );
}

// 4. 懒加载执行函数
function loadLazyImages() {
  lazyImages.forEach(img => {
    // 若图片已加载或无真实地址,跳过
    if (img.src === img.dataset.src || !img.dataset.src) return;
    
    // 检查图片是否在可视区域
    if (isInViewport(img)) {
      // 创建临时Image对象预加载图片(避免页面闪烁)
      const tempImg = new Image();
      tempImg.src = img.dataset.src;
      
      // 图片加载完成后替换src,并清理属性
      tempImg.onload = () => {
        img.src = tempImg.src;
        img.removeAttribute('data-src'); // 移除自定义属性
      };
      
      // 处理加载失败
      tempImg.onerror = () => {
        img.src = 'https://via.placeholder.com/100x100?text=加载失败';
      };
    }
  });
}

// 5. 初始加载与滚动监听
loadLazyImages(); // 加载首屏图片
window.addEventListener('scroll', loadLazyImages); // 滚动时检查新图片

代码执行流程

  1. 初始状态

    • 图片src为占位图(小尺寸加载快),真实地址存于data-src
    • 浏览器解析 HTML 后,首屏图片通过loadLazyImages()检查并加载。
  2. 滚动场景

    • 用户滚动页面时,scroll事件触发loadLazyImages()
    • 函数遍历所有图片,用getBoundingClientRect()判断位置;
    • 符合条件的图片通过new Image()预加载,完成后替换src
  3. 优化效果

    • 仅加载可视区域图片,减少初始请求量;
    • 预加载机制确保图片显示时已缓存,避免闪烁。

四、性能优化

示例代码存在几个缺点:

滚动事件高频触发:未使用节流 / 防抖,每秒可触发 60 次计算,导致浏览器卡顿

重复遍历图片:已加载图片仍被反复判断,浪费 CPU 资源

我们可以添加节流处理(避免频繁滚动触发)

function throttle(func, delay) {
  let timer;
  return function() {
    if (timer) return;
    timer = setTimeout(() => {
      func.apply(this, arguments);
      timer = null;
    }, delay);
  };
}
window.addEventListener('scroll', throttle(loadLazyImages, 200)); // 200ms执行一次

核心作用:限制函数在指定时间内的执行频率,避免高频触发导致性能问题。

The End

以上内容就是关于图片懒加载的实现。