鲁斯前端布鲁斯前端

文章中英模式

常见的前端面试题目 - 页面加载 - Lazy Loading 是什麼?

深入解析前端 Lazy Loading 优化技术:图片 Lazy Loading、组件 Lazy Loading 及 IntersectionObserver API 的原理实现,以及 Next.js 代码分割技术的完整剖析。

影片縮圖

懒得看文章?那就来看视频吧

基本概念

Lazy Loading(懒加载)是优化网页性能的技术,核心是延迟加载非必要资源(有看到才加载,没看到先等等),等到真正需要时才载入。这样能大幅提升页面初始载入速度,省下资源,让用户体验更好。

传统加载流程:
所有资源同时加载 → 页面渲染完成 → 用户可交互

Lazy Loading 流程:
关键资源加载(有看到才加载) → 页面初步渲染 → 用户可交互 → 其他资源按需加载

前端开发中,Lazy Loading 主要应用场景:

应用场景实际例子
图片和媒体文件电商网站的产品图片、社交媒体的视频内容
UI 组件和页面区块页面底部的评论区、弹出式对话框

图片 Lazy Loading

传统图片加载 vs Lazy Loading

传统网页会一次性下载所有 <InlineCode><img></InlineCode> 标签的图片,无论是否可见,浪费资源且降低页面加载速度。

传统图片加载:
┌──────────────────────────────────────────┐
│                                          │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐│  所有图片
│  │ 图片 1    │  │ 图片 2    │  │ 图片 3    ││  同时请求
│  └──────────┘  └──────────┘  └──────────┘│
│                                          │◄───────
│  ┌──────────┐  ┌──────────┐  ┌──────────┐│  用户仅
│  │ 图片 4    │  │ 图片 5    │  │ 图片 6    ││  看到这里
│  └──────────┘  └──────────┘  └──────────┘│
│                                          │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐│  未显示的
│  │ 图片 7    │  │ 图片 8    │  │ 图片 9    ││  也被加载
│  └──────────┘  └──────────┘  └──────────┘│
│                                          │
└──────────────────────────────────────────┘

Lazy Loading 只在图片接近或进入视窗时才加载,大幅减少初始页面负载。

 Lazy Loading 图片:
┌──────────────────────────────────────────┐
│                                          │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐│  已加载
│  │ 图片 1    │  │ 图片 2    │  │ 图片 3    ││  (在视窗中)
│  └──────────┘  └──────────┘  └──────────┘│
│                                          │◄───────
│  ┌──────────┐  ┌──────────┐  ┌──────────┐│  正在加载
│  │ 图片 4    │  │ 加载中... │  │ 加载中... ││  (接近视窗)
│  └──────────┘  └──────────┘  └──────────┘│
│                                          │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐│  尚未加载
│  │ 未加载    │  │ 未加载    │  │ 未加载    ││  (远离视窗)
│  └──────────┘  └──────────┘  └──────────┘│
│                                          │
└──────────────────────────────────────────┘

实现图片 Lazy Loading 的方式

1. 原生 loading='lazy' 属性

现代浏览器内建支持的 Lazy Loading 属性,简单易用,无需额外 JavaScript。

<img src="image.jpg" loading="lazy" alt=" Lazy Loading 圖片" />

2. IntersectionObserver API

更灵活的 Lazy Loading 方式,监控元素是否进入视窗,性能好。

// 图片 Lazy Loading 基本实现
const lazyImages = document.querySelectorAll('.lazy-image');

const imageObserver = new IntersectionObserver((entries, observer) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src; // 加载真实图片
      img.classList.remove('lazy-image');
      observer.unobserve(img); // 加载后停止观察
    }
  });
});

// 对每个 Lazy Loading 图片进行观察
lazyImages.forEach(img => {
  imageObserver.observe(img);
});

组件 Lazy Loading

Skeleton 占位符技术

Skeleton(骨架屏)是组件加载中的过渡技术,在内容加载前显示轮廓,让用户等待体验更好。

 Lazy Loading 组件流程:
┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│                 │     │ ░░░░░░░░░░░░░░░ │     │ 标题文字         │
│                 │     │ ░░░░░░░░░░░     │     │ 实际内容段落     │
│    空白区域      │ ──► │ ░░░░░░░░░░░░░░░ │ ──►  │ 图片和其他       │
│  (不好的体验)    │     │ ░░░░░░░░░░░     │      │ 交互元素         │
│                 │     │ ░░░░░░░░░░░░░░░ │     │                 │
└─────────────────┘     └─────────────────┘     └─────────────────┘
      初始状态            Skeleton 占位           完整内容加载

好的 Skeleton 设计有以下特点:

  • 1. 和实际内容有相似的结构
  • 2. 用动画效果提示'正在加载'
  • 3. 用适当的颜色,不干扰视觉
function CardSkeleton() {
  return (
    <div className="card-skeleton">
      <div className="skeleton-img pulse"></div>
      <div className="skeleton-title pulse"></div>
      <div className="skeleton-text pulse"></div>
      <div className="skeleton-text pulse"></div>
    </div>
  );
}

function ProductCard({ loading, product }) {
  if (loading) {
    return <CardSkeleton />;
  }
  
  return (
    <div className="product-card">
      <img src={product.image} alt={product.name} />
      <h3>{product.name}</h3>
      <p>{product.description}</p>
      <button>加入購物車</button>
    </div>
  );
}

Window.IntersectionObserver API 深入理解

IntersectionObserver 是实现 Lazy Loading 的好工具,它能监测元素与视窗的交叉状态变化。

IntersectionObserver 工作原理:
┌─────────────────────────────────────────┐
│                                         │
│                视口 (Viewport)           │
│                                         │
│  ┌─────────────────────────────────┐    │
│  │                                 │    │
│  │  可见区域 (Visible Area)         │    │
│  │                                 │    │
│  └─────────────────────────────────┘    │
│                   ▲                     │
│                   │                     │
│  ┌─────────────┐  │  ┌─────────────┐    │
│  │ 元素 A       │  │  │ 元素 B      │    │
│  │ isInter-    │  │  │ isInter-    │    │
│  │ secting:    │  │  │ secting:    │    │
│  │ false       │  │  │ true        │    │
│  └─────────────┘  │  └─────────────┘    │
│                   │                     │
└───────────────────┼─────────────────────┘
                交叉状态变化时触发回调

核心优势

  • 1. 减少初始页面加载时间
  • 2. 降低服务器负担和省流量
  • 3. 在手机上性能更好
  • 4. 减少资源浪费(用户可能永远不会滚到某些内容)
// IntersectionObserver 配置示例
const options = {
  root: document.querySelector('#scrollArea'), // 默认为视窗
  rootMargin: '0px 0px 100px 0px', // 提前100px触发
  threshold: [0, 0.5, 1] // 交叉比例阈值
};

const observer = new IntersectionObserver((entries, observer) => {
  entries.forEach(entry => {
    // 元素进入视窗
    if (entry.isIntersecting) {
      console.log('元素进入视窗,可见比例: ' + entry.intersectionRatio);
    }
  });
}, options);

// 开始观察元素
observer.observe(document.querySelector('#targetElement'));

典型应用场景

  1. 1. 图片/视频 Lazy Loading :元素进入视窗才加载
  2. 2. 无限滚动:到底部就跟后端获取数据,加载更多内容
// 无限滚动示例
function createInfiniteScroll(callback) {
  // 创建观察者
  const observer = new IntersectionObserver(entries => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        // 触发加载更多内容
        callback();
      }
    });
  }, { rootMargin: '0px 0px 200px 0px' }); // 提前200px触发
  
  // 观察触发元素(通常放在列表底部)
  const trigger = document.querySelector('#loadMoreTrigger');
  observer.observe(trigger);
  
  return {
    disconnect: () => observer.disconnect()
  };
}

// 使用示例
const infiniteScroll = createInfiniteScroll(() => {
  loadMoreItems(); // 加载更多项目的函数
});

Lazy Loading 最佳实践

性能考量

  • 1. 设定合理预加载距离,提前载入即将看到的内容
  • 2. 避免同时 Lazy Loading 太多元素
  • 3. 为无 JavaScript 环境提供备选方案
  • 4. 考虑网速差异,提供低质量预览
<!-- 渐进式 Lazy Loading  -->
<img 
  src="低质量预览.jpg" 
  data-src="高质量图片.jpg" 
  loading="lazy" 
  class="lazy-image" 
  alt=" Lazy Loading 图片" 
/>

<noscript>
  <!-- 无 JavaScript 的备选方案 -->
  <img src="高质量图片.jpg" alt=" Lazy Loading 图片" />
</noscript>

用户体验优化

  • 1. 避免加载时造成页面跳动
  • 2. 用占位符保留空间
  • 3. 给予适当加载反馈
  • 4. 处理快速滚动情况
/* 防止页面跳动的CSS */
.image-container {
  position: relative;
  aspect-ratio: 16 / 9; /* 或用 padding-top 技巧 */
  background-color: #f0f0f0; /* 占位色 */
  overflow: hidden;
}

.image-container img {
  position: absolute;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 0.3s ease;
}

.image-container img.loaded {
  opacity: 1;
}
// 加载完成后平滑显示
function onImageLoad(img) {
  img.classList.add('loaded');
}

// 使用低质量预览图技术
function loadHighQualityImage(img) {
  const highResImage = new Image();
  highResImage.src = img.dataset.src;
  highResImage.onload = () => {
    img.src = highResImage.src;
    onImageLoad(img);
  };
}

🔥 常见面试题目

(一)什么是 Lazy Loading?它解决了什么问题?

解答:

Lazy Loading 是延迟加载页面中不立即需要的资源,等用户真正需要时才加载。

它解决的问题:

  • 1. 减少初始页面加载时间
  • 2. 降低服务器负担和省流量
  • 3. 在手机上性能更好
  • 4. 减少资源浪费(用户可能永远不会滚到某些内容)

(二)如何处理 Lazy Loading 过程中的用户体验问题?

解答:

Lazy Loading 的用户体验关键点:

防止页面跳动:

  1. 1. 图片加载前先设好固定尺寸
  2. 2. 用 CSS aspect-ratio 或 padding-top 保留空间

加载状态提示:

  1. 1. 用 Skeleton 骨架屏
  2. 2. 加载完成后淡入显示
  3. 3. 先显示模糊预览图

处理失败情况:

  1. 1. 设置重试机制
  2. 2. 提供友好错误提示
  3. 3. 准备备用内容

特殊情况优化:

  1. 1. 快速滚动时的加载优先顺序
  2. 2. 预加载即将需要的内容
  3. 3. 根据网速调整加载策略