懒加载:让网页加载像“点外卖”一样高效!🚀

97 阅读7分钟

🍔 为什么需要懒加载?

想象一下,你点了一份外卖,但商家一次性把所有菜品(50+ 图片)都送上门 😅。
结果你的手机卡顿得像老式拨号上网 🐢,这显然不合理!
懒加载就是告诉商家:“先给我首屏的菜,其他的等我滑动再送!”

屏幕录制 2025-07-11 221959.gif


🧠 懒加载的核心原理

image.png

1. 为什么需要懒加载?

  • 首屏加载速度:用户只看首屏,没必要一开始就加载全部图片!
  • 网络带宽有限:并发请求太多,就像一条公路挤满车,容易堵 😱。
  • 用户体验:电商页面加载50+图片?不懒加载=用户直接关掉网页!

2. 可视区检测:传统 vs. 现代

方法问题
getBoundingClientRect每次滚动都要计算位置,触发回流(浏览器重新计算布局)😱
IntersectionObserver像“智能管家”,只在图片进入视口时通知你,异步且无回流 🚀

3. 占位图:小而美

  • src 设置占位图(如 loading.gif),避免空白区域吓到用户 😅。
  • data-original 存储真实图片地址,只在需要时加载

🛠️ IntersectionObserver 实现懒加载

image.png

✅ 新版代码示例(observer.html

<!-- HTML 结构:用 data-original 存储真实图片地址 -->
<img data-original="https://example.com/image1.jpg" class="lazy-img" src="loading.gif">
<img data-original="https://example.com/image2.jpg" class="lazy-img" src="loading.gif">
// JavaScript:用 IntersectionObserver 监听图片是否进入视口
const observer = new IntersectionObserver((changes) => {
  changes.forEach((element) => {
    if (element.intersectionRatio > 0) { // 图片进入视口
      const img = new Image();
      img.src = element.target.dataset.original; // 从 data-original 获取真实地址
      img.onload = () => {
        element.target.src = img.src; // 替换为真实图片 🚀
      };
      observer.unobserve(element.target); // 加载完成后停止监听
    }
  });
}, { 
  rootMargin: '0px', // 可视区外延(提前加载)
  threshold: 0.1 // 当图片 10% 进入视口时触发
});

// 监听所有 .lazy-img 图片
document.querySelectorAll('.lazy-img').forEach((img) => {
  observer.observe(img);
});

🔄 新旧实现对比:scroll vs. IntersectionObserver

方法优点缺点
scroll 事件实现简单 🛠️触发频繁,性能差 😱
IntersectionObserver异步高效 🚀需要理解回调函数 💡

传统 scroll 实现(性能差)

window.addEventListener('scroll', () => {
  document.querySelectorAll('.lazy-img').forEach((img) => {
    if (isInViewport(img)) {
      img.src = img.dataset.original;
    }
  });
});

🧩 性能优化技巧

image.png

  1. 避免频繁触发 scroll
    • IntersectionObserver 是异步的,比 onscroll + 防抖更高效!
  2. 占位图要轻量
    • loading.gif 代替大图,节省带宽 🌐。
  3. IntersectionObserver 高级用法
    • rootMargin: 提前加载图片(如 rootMargin: "0px 0px -100px 0px" 表示图片进入视口上方100px时加载)。
    • threshold: 控制触发时机(如 threshold: 0.5 表示图片50%进入视口时触发)。

🚀 实际应用场景

电商页面加载50+图片

  • 不懒加载:用户打开页面就卡顿 😱。

image.png

  • 懒加载:首屏秒开,滑动加载其他图片,体验丝滑!

image.png


📚 总结与扩展

  • 懒加载的核心按需加载,减少首屏压力
  • 推荐工具:优先使用 IntersectionObserver,告别 scroll 的性能陷阱。
  • 动手实践GitHub 示例代码 🔧

❓思考题

  • 如果用户快速滚动页面,懒加载会失效吗?
  • 如何用 IntersectionObserver 实现“图片预加载”(在用户看到前几秒加载)?

🎉 附:代码注释解析

// 创建 IntersectionObserver 实例,监听图片是否进入视口
const observer = new IntersectionObserver((changes) => {
  changes.forEach((element) => {
    if (element.intersectionRatio > 0) { // 图片进入视口
      const img = new Image(); // 创建新图片对象
      img.src = element.target.dataset.original; // 从 data-original 获取真实地址
      img.onload = () => {
        element.target.src = img.src; // 替换为真实图片 🚀
      };
      observer.unobserve(element.target); // 加载完成后停止监听
    }
  });
}, { 
  rootMargin: '0px', // 可视区外延(提前加载)
  threshold: 0.1 // 当图片 10% 进入视口时触发
});

🧪 IntersectionObserver 深度解析

image.png

1. IntersectionObserver 的工作原理

IntersectionObserver 是浏览器提供的一种异步 API,它通过观察目标元素与祖先元素或视口的交叉状态来触发回调函数。

核心特性:

  • 异步触发:浏览器在空闲时自动检测元素是否进入视口,无需手动绑定事件(如 scroll)。
  • 无回流:不会频繁触发布局计算,避免性能损耗。
  • 高精度控制:通过 thresholdrootMargin 精准控制加载时机。

回调函数的触发条件:

  • 当目标元素与根元素(默认是视口)的交叉比例intersectionRatio)超过设定的 threshold 时,回调函数被触发。
  • 例如:threshold: 0.1 表示当图片 10% 进入视口时触发加载。

代码示例:

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      // 元素进入视口
      entry.target.src = entry.target.dataset.original;
    }
  });
});

🧩 Array.from 与 DOM 操作

image.png

1. Array.from 的作用

在 JavaScript 中,document.querySelectorAll 返回的是一个 NodeList(类数组对象),无法直接使用数组的方法(如 forEachmap)。

为什么需要 Array.from

  • 兼容性:旧版浏览器可能不支持 NodeList 的数组方法。
  • 灵活性:将类数组对象转换为真正的数组,方便后续操作。

代码示例:

const lazyImgs = document.querySelectorAll('.lazy-img');
const lazyImgsArray = Array.from(lazyImgs); // 转换为数组

lazyImgsArray.forEach(img => {
  observer.observe(img); // 监听每个图片
});
替代方案:
  • 使用 spread 操作符:
    const lazyImgsArray = [...document.querySelectorAll('.lazy-img')];
    

🧠 img.onload 的必要性

image.png

1. 为什么需要 onload 事件?

在懒加载中,图片的 src 是动态赋值的(从 data-original)。如果直接将 src 赋值给目标图片,可能会导致:

  • 图片未加载完成:页面布局抖动或空白区域出现。
  • 错误加载:图片地址错误时,页面可能崩溃。

onload 的作用:

  • 确保图片完全加载后再替换 src,避免布局抖动。
  • 捕获加载错误:通过 onerror 处理失败情况。

代码示例:

const img = new Image();
img.src = element.target.dataset.original;
img.onload = () => {
  element.target.src = img.src; // 替换为真实图片 🚀
};
img.onerror = () => {
  console.error('图片加载失败:', element.target.dataset.original);
};

🛠️ IntersectionObserver 的高级用法

image.png

1. rootMargin:提前加载图片

  • rootMargin 可以设置一个“提前加载”的范围,让图片在进入视口前就开始加载。
  • 例如:rootMargin: "0px 0px -100px 0px" 表示图片在进入视口上方 100px 时触发加载。

代码示例:

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.intersectionRatio > 0) {
      // 加载图片
    }
  });
}, { 
  rootMargin: "0px 0px -100px 0px" // 提前加载
});

2. threshold:控制触发精度

  • threshold 是一个数组或单个数值,表示触发回调的交叉比例阈值。
  • 例如:threshold: [0, 0.5, 1] 表示当图片 0%、50%、100% 进入视口时触发回调。

代码示例:

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.intersectionRatio >= 0.5) {
      // 图片 50% 进入视口时加载
    }
  });
}, { 
  threshold: [0.5] 
});

🧪 图片预加载与占位图缓存

image.png

1. 图片预加载

在用户看到图片前几秒加载图片,可以提升体验。

  • 实现方式:结合 IntersectionObserverrootMargin 提前加载。

代码示例:

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.intersectionRatio > 0) {
      const img = new Image();
      img.src = entry.target.dataset.original;
      img.onload = () => {
        entry.target.src = img.src;
      };
    }
  });
}, { 
  rootMargin: "0px 0px -200px 0px" // 提前加载
});

2. 占位图的缓存策略

  • 缓存占位图:通过 HTTP 头设置 Cache-Control: max-age=31536000 让浏览器长期缓存占位图。
  • CDN 加速:使用 CDN 分发占位图,减少服务器压力。

🧠 动态内容懒加载

image.png

1. 无限滚动的懒加载

在动态加载内容(如无限滚动)时,需要监听新插入的图片

  • 解决方案:使用 MutationObserver 监听 DOM 变化,自动注册新的懒加载图片。

代码示例:

const mutationObserver = new MutationObserver((mutations) => {
  mutations.forEach(mutation => {
    mutation.addedNodes.forEach(node => {
      if (node.nodeType === 1 && node.classList.contains('lazy-img')) {
        observer.observe(node);
      }
    });
  });
});

mutationObserver.observe(document.body, { childList: true, subtree: true });

🧪 IntersectionObserver 的兼容性

image.png

1. 兼容性处理

  • 旧版浏览器:不支持 IntersectionObserver,需要使用 scroll 或第三方库(如 lazysizes)。
  • 回退方案:检测是否支持 IntersectionObserver,否则使用 scroll

代码示例:

if ('IntersectionObserver' in window) {
  // 使用 IntersectionObserver
} else {
  // 使用 scroll 事件
  window.addEventListener('scroll', () => {
    document.querySelectorAll('.lazy-img').forEach(img => {
      if (isInViewport(img)) {
        img.src = img.dataset.original;
      }
    });
  });
}

📚 懒加载与 SEO

1. SEO 优化

  • 图片的 alt 属性:为图片添加 alt 描述,帮助搜索引擎理解图片内容。
  • 延迟加载的 SEO 影响:部分搜索引擎可能无法抓取延迟加载的图片,建议在首屏或重要区域直接加载。

🎉 总结:懒加载的终极武器库

1. IntersectionObserver 的优势

  • 异步高效:避免频繁触发回流。
  • 高精度控制:通过 thresholdrootMargin 精准加载。

2. Array.from 与 DOM 操作

  • 类数组转数组:确保兼容性。
  • 灵活处理 DOM 元素:结合 forEachmap 实现批量操作。

3. img.onload 的必要性

  • 确保图片加载完成:避免布局抖动。
  • 错误处理:通过 onerror 捕获异常。

4. 补充技巧

  • 图片预加载:提前加载图片提升体验。
  • 占位图缓存:减少重复请求。
  • 动态内容懒加载:结合 MutationObserver 自动注册新图片。
  • 兼容性处理:回退到 scroll 事件。

🧠 思考题答案与扩展

  • Q1: 如果用户快速滚动页面,懒加载会失效吗?

    • A: 不会!IntersectionObserver 会在浏览器空闲时自动检测,即使用户快速滚动,也能捕获到图片进入视口的瞬间。
  • Q2: 如何用 IntersectionObserver 实现“图片预加载”?

    • A: 通过 rootMargin 提前加载图片(如 rootMargin: "-200px"),让图片在进入视口前几秒开始加载。

🚀 动手实践:GitHub 示例代码

  • 项目链接WildBlue58-GitHub 示例代码
  • 功能:包含 IntersectionObserverscroll 两种实现方式,支持图片预加载和占位图缓存。

通过这篇文章,你已经掌握了懒加载的原理、实现方式以及性能优化技巧!快去试试用 IntersectionObserver 优化你的网页吧 🚀!