3分钟教会你图片加载神技,告别卡顿

大家好,今天,咱们来聊聊一个在面试中几乎“必考”的经典问题:常见图片懒加载方式有哪些? 别小看这个问题——它在面试中的出现频率很高!为什么?因为懒加载(Lazy Loading)是前端性能优化的核心技能之一,面试官用它来“一箭三雕”:

  • 用户体验意识:理解页面加载性能(特别是首屏时间)对用户留存率的关键影响。
  • 技术实现能力:考察对DOM操作、事件处理、浏览器API及性能优化技巧的掌握。
  • 问题解决思维:评估将性能优化理论转化为实际解决方案的能力。
那懒加载到底用在哪儿?简单说,就是当页面太长或图片太多时,不让所有图片一股脑儿加载(否则用户得等半天),而是等用户滚动到附近再加载。比如:电商商品列表、新闻瀑布流、相册画廊——这些场景下,懒加载能减少首次加载时间、节省带宽,还能提升页面流畅度。下面,我就带大家深入拆解几种常见方式。

一、懒加载的应用场景与价值

在深入原理前,先看看懒加载的典型场景。想象一下:你打开一个电商App,首页有100张商品图。如果全加载,用户得盯着空白屏等10秒!懒加载在这里大显身手:只加载首屏图片,用户往下滚时,才动态加载新图片。这不仅让页面“秒开”,还减少了服务器压力(比如带宽节省30%以上)。常见场景包括:

  • 长列表页面:如社交媒体动态流,用户滚动时才加载新内容。
  • 图片密集型网站:如摄影画廊或电商平台,避免一次性请求过多资源。
  • 移动端优化:在网速慢的环境下,懒加载能显著提升体验。

接下来进入正题——懒加载的实现原理。

二、实现原理一:Intersection Observer API(推荐方案)

专业术语解释:

Intersection Observer API是浏览器原生提供的接口,用来高效监测目标元素(比如图片)是否进入或离开视口(viewport,即用户当前看到的屏幕区域)。它基于“观察者模式”——你注册一个“观察员”(observer),浏览器自动在后台计算元素位置,触发回调。相比传统方法,它更省资源,因为避免了频繁的滚动事件计算。

原理与优势:

Intersection Observer API是浏览器提供的原生接口,用于高效、异步地监测目标元素与其祖先元素或顶级文档视口(Viewport)的交叉状态(Intersection)。它采用观察者模式,开发者注册一个观察器(Observer),浏览器引擎负责在后台计算目标元素的可见性变化,并在满足阈值条件时触发回调函数。

核心优势:

  • 高性能:避免手动监听滚动事件和频繁调用getBoundingClientRect()带来的布局抖动(Layout Thrashing)和性能开销,由浏览器底层优化计算。
  • 配置灵活:可设置根元素(root)、预加载边距(rootMargin)、可见性触发阈值(threshold)。
  • 代码简洁易维护。

核心实现代码与解析:

// 步骤1:创建 IntersectionObserver 实例
const imgObserver = new IntersectionObserver((entries, observer) => {
  entries.forEach(entry => {
    // 当元素进入视口(或满足阈值条件)
    if (entry.isIntersecting) {
      const lazyImage = entry.target;
      // 将 data-src 存储的真实URL赋给 src 属性,触发图片加载
      lazyImage.src = lazyImage.dataset.src;
      // 可选:移除 data-src 属性或添加标记,避免重复处理
      lazyImage.removeAttribute('data-src');
      // 图片加载成功后,停止观察该元素
      observer.unobserve(lazyImage);
    }
  });
}, {
  root: null, // 观察相对于视口 (null 或未指定即默认为视口)
  rootMargin: '0px 0px 200px 0px', // 观察区域向外扩展(下边扩展200px,实现提前加载)
  threshold: 0.1 // 目标元素10%可见时触发回调(可设为数组[0, 0.1, 0.5, 1]监测多个阈值点)
});

// 步骤2:获取所有需懒加载的图片元素(使用 data-src 而非 src)
const lazyImages = document.querySelectorAll('img[data-src]');
// 为每个图片元素注册观察
lazyImages.forEach(image => {
  imgObserver.observe(image);
});

代码解析与关键点:

  • data-src属性:初始 HTML 中,图片的src属性设置为一个轻量占位符(如 1x1 透明像素图)或为空,将真实图片 URL 存储在data-src属性中。这是懒加载的标准实践模式。
  • IntersectionObserver回调:当被观察元素 (entry.target) 的交叉状态满足threshold条件时(entry.isIntersecting === true),执行加载逻辑。
  • 加载真实资源:data-src的值赋给src属性,浏览器自动发起图片请求。
  • 停止观察 (unobserve):加载完成后调用unobserve()解除对该元素的观察,避免不必要的后续计算。
  • 配置参数 (rootMargin):设置rootMargin: '0px 0px 200px 0px'表示观察区域向下扩展 200px。这使得图片在滚动到距离视口底部还有 200px 时就开始加载,实现平滑的“预加载”效果,提升用户体验。threshold控制触发回调的可见比例阈值。
  • 性能优势:浏览器内部优化交叉状态计算,显著优于基于滚动事件的方案。

兼容性与注意事项:

  • 兼容性:现代浏览器(Chrome, Firefox, Safari, Edge)均提供良好支持。Internet Explorer 不支持。可通过添加Intersection Observer polyfill实现兼容。
  • 原生loading="lazy"HTML 提供了原生懒加载属性 (<img src="image.jpg" loading="lazy" alt="...">)。其兼容性在快速提升,但尚未完全普及(尤其旧版浏览器)。可作为渐进增强方案与 JS 实现结合使用。Intersection Observer方案提供了更精细的控制能力。
  • 阈值 (threshold):可设置为数组(如[0, 0.25, 0.5, 0.75, 1]),在元素达到不同可见比例时多次触发回调,适用于复杂场景(如元素进入/离开视口的不同动画效果)。


三、实现原理二:监听滚动事件 + getBoundingClientRect()(兼容方案)

专业术语解释:

这种方式基于事件监听——给window添加scroll事件,当用户滚动时,用getBoundingClientRect()方法计算每个图片相对于视口的位置。如果图片进入或接近视口,则动态设置src属性加载图片。它兼容性好(支持所有浏览器),但性能开销大,因为scroll事件触发频繁,需要手动优化(如节流)。

原理与适用场景:

此方案是传统的实现方式,其核心逻辑是:

  • 监听windowscroll事件(以及resize事件)。
  • 在事件处理函数中,遍历所有待加载的图片元素。
  • 使用Element.getBoundingClientRect()方法计算每个元素相对于视口的位置。
  • 判断元素是否进入(或接近)视口,若满足条件则加载图片。

核心价值:

  • 近乎 100% 的浏览器兼容性。
  • 适用于无法使用 Polyfill 或必须兼容极旧浏览器的场景。

主要缺点:

  • 性能开销大:scrollresize事件触发频率极高,频繁调用计算密集型方法getBoundingClientRect()会导致严重的性能问题(重排 Reflow),造成页面卡顿。

核心实现代码与优化(节流):

// 步骤1:定义懒加载检查函数
function lazyLoadTraditional() {
  const lazyImages = document.querySelectorAll('img[data-src]'); // 获取待加载图片
  lazyImages.forEach(img => {
    // 获取元素相对于视口的边界矩形
    const rect = img.getBoundingClientRect();
    // 判断元素是否进入视口 (上边界 < 视口高度 且 下边界 > 0)
    if (rect.top < window.innerHeight && rect.bottom > 0) {
      img.src = img.dataset.src; // 加载图片
      img.removeAttribute('data-src'); // 移除标记,避免重复处理
    }
  });
}

// 步骤2:添加带节流优化的滚动事件监听器
let isThrottled = false;
const throttleDelay = 200; // 节流延迟时间(ms)

window.addEventListener('scroll', () => {
  if (!isThrottled) {
    isThrottled = true;
    // 使用 setTimeout 实现基础节流
    setTimeout(() => {
      lazyLoadTraditional();
      isThrottled = false;
    }, throttleDelay);
  }
});
// 添加 resize 事件监听(同样需要节流)
window.addEventListener('resize', () => {
  if (!isThrottled) {
    isThrottled = true;
    setTimeout(() => {
      lazyLoadTraditional();
      isThrottled = false;
    }, throttleDelay);
  }
});

// 步骤3:初始化 - 页面加载时检查首屏图片
document.addEventListener('DOMContentLoaded', lazyLoadTraditional);
// 或确保在页面初始布局完成后执行
window.addEventListener('load', lazyLoadTraditional);

代码解析与关键点:

  • getBoundingClientRect()返回元素的大小及其相对于视口的位置(top,right,bottom,left,width,height)。top是元素顶部到视口顶部的距离(视口坐标系,上正下负)。
  • 视口内判断:rect.top < window.innerHeight表示元素顶部在视口底部上方(即已进入或部分进入视口)。rect.bottom > 0表示元素底部在视口顶部下方(防止元素完全在视口上方时误判)。两者结合确保元素与视口有交叉。
  • 性能优化 - 节流 (Throttling):这是必须的优化手段。节流确保在一段时间内(throttleDelay, 如 200ms)只执行一次lazyLoadTraditional函数,大幅减少getBoundingClientRect()的调用频率。示例使用了简单的setTimeout节流。更优方案是使用requestAnimationFrame结合标志位或 lodash 的_.throttle
  • resize事件:窗口大小改变也会影响元素是否在视口内,因此也需要监听并节流处理。
  • 初始化加载:页面加载完成时(DOMContentLoadedload),必须主动调用一次lazyLoadTraditional加载首屏图片。


四、总结:技术选型与最佳实践

懒加载不是“可有可无”,而是前端性能的“守门员”。两种主流方式各有千秋。

核心原则:在现代浏览器项目中,优先使用 Intersection Observer API;对于需要兼容旧浏览器的场景,使用基于滚动事件的方案并务必实施节流优化。

注意,懒加载不是万能药,过度使用可能影响SEO(搜索引擎爬虫可能不执行JS),所以要用<img>标签配合原生属性(如loading="lazy")作为补充。未来,随着浏览器进化,原生懒加载会更普及,但原理不变——面试时,需要抓住“性能优化”这根主线。

五、面试秘籍:如何征服“懒加载”问题

面对“图片懒加载”相关问题,建议结构化回答:

  1. 清晰定义与价值:

    “图片懒加载是一种延迟加载非可视区域图片资源的技术,核心目标是优化首屏加载性能(提升 FCP/LCP)、节省带宽、改善用户体验(尤其在长列表/图片密集页面和弱网环境)。”

  2. 阐述主流方案与对比:

    “主要有两种实现方式:首选是现代浏览器支持的Intersection Observer API,它性能高效、配置灵活、代码简洁;其次是兼容性更好的传统方案,通过监听scroll/resize事件并结合getBoundingClientRect()计算元素位置,但必须使用节流优化性能。”

  3. 深入核心实现细节:

    “使用Intersection Observer的关键步骤是:1) 创建observer实例,配置rootMargin(如提前200px加载) 和threshold;2) 用querySelectorAll获取带data-src的图片;3) 在回调中检测entry.isIntersecting,若为true则将data-src赋给src并调用unobserve()。代码大致如... [简述关键代码行]。”

  4. 提及优化与注意事项:

    “实践中需注意:使用data-src和占位符、设置图片尺寸避免布局偏移、考虑兼容性和 Polyfill、进行错误处理。还需关注对 SEO 的潜在影响,可通过<noscript>标签或结合原生loading="lazy"属性缓解。”

  5. 总结与关联:
    “懒加载是性能优化体系中的重要环节,通常与图片压缩、CDN、响应式图片等技术结合使用。在项目选型上,优先采用 Intersection Observer API以获取最佳性能和开发体验。” 展现对技术选型的思考。

面试就像打游戏——这个问题是“小Boss”,答好了就能升级!关键态度是需要展现对原理的清晰理解、对不同方案的优缺点认知、对实际工程细节(性能、兼容性、SEO)的考量,以及将理论落地的能力。保持专业、自信、条理清晰。

这篇文章就到这里。如果你有更多问题,欢迎留言讨论——我是你们的面试战友,下期见!(别忘了转发给正在求职的小伙伴,一起上岸!)


写在最后


还没有使用过我们的刷题网站(https://fe.ecool.fun/)或者小程序前端面试题宝典的同学,如果近期准备或者正在找工作,千万不要错过,题库主打题全和更新快哦~。
有会员购买、辅导咨询的小伙伴,可以通过下面的二维码,联系我们的小助手。