前端性能优化:图片懒加载全攻略

简介

在对项目进行性能优化时,最常见的优化就是图片的懒加载,懒加载的目的是延迟加载页面上的图片,只有当图片进入视口的时候才加载,这样可以减少初始页面加载时间,节省带宽,提升用户体验。

懒加载有什么好处?

  1. 减少首屏加载时间:仅加载用户可见区域的图片,降低首次渲染阻塞。
  2. 节省带宽和流量:避免加载用户可能永远不会看到的内容(如长页面底部的图片)。
  3. 提升用户体验:防止页面卡顿,尤其是移动端和弱网环境。
  4. 优化SEO和性能评分:直接影响 Lighthouse 等工具的性能评分指标(如 LCP、FCP)。

在vue项目中(使用 vue-lazyload/vue3-lazyload 库)

  1. 安装库

    bash 复制代码
    npm install vue-lazyload --save
  2. 全局配置 (在 main.js 中):

    javascript 复制代码
    import Vue from 'vue';
    import VueLazyload from 'vue-lazyload';
    
    Vue.use(VueLazyload, {
      preLoad: 1.3, // 预加载比例
      loading: 'path/to/loading.gif', // 占位图
      error: 'path/to/error.png',     // 加载失败图
      attempt: 3                      // 重试次数
    });
  3. 在组件中使用

    html 复制代码
    <template>
      <img v-lazy="imageUrl" alt="description">
    </template>

在React 项目中(使用 react-lazyload 库)

  1. 安装库

    bash 复制代码
    npm install react-lazyload --save
  2. 包裹组件

    jsx 复制代码
    import LazyLoad from 'react-lazyload';
    
    function MyComponent() {
      return (
        <LazyLoad height={200} offset={100} placeholder={<LoadingSpinner />}>
          <img src="image.jpg" alt="description" />
        </LazyLoad>
      );
    }

原生JavaScript的实现方法

原生js的实现方法有两种:使用Intersection Observer API和通过监听滚动事件(传统方案)。其中,Intersection Observer更现代,性能更好,而滚动事件监听则是传统方法,兼容性可能更好,但性能较差。

方案1:使用 Intersection Observer API

实现步骤:

  1. 将图片的真实URL存放在data-src属性中
  2. 当图片进入可视区域时,将data-src赋值给src属性
  3. 使用IntersectionObserver监听元素可见性变化
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>图片懒加载示例</title>
  <style>
    .lazy-img {
      width: 100%;
      height: 600px; /* 根据实际需求设置高度 */
      background: #f0f0f0;
      margin: 20px 0;
      object-fit: cover;
    }
  </style>
</head>
<body>
  <!-- 图片容器:真实图片地址存储在data-src -->
  <img class="lazy-img" data-src="https://picsum.photos/800/600?image=1" alt="图片1">
  <img class="lazy-img" data-src="https://picsum.photos/800/600?image=2" alt="图片2">
  <!-- 更多图片... -->

  <script>
    document.addEventListener('DOMContentLoaded', () => {
      // 获取所有需要懒加载的图片元素
      const lazyImages = document.querySelectorAll('.lazy-img');
      
      // 配置Intersection Observer
      const observerOptions = {
        root: null, // 使用视口作为观察区域
        rootMargin: '0px 0px 200px 0px', // 提前200px触发加载
        threshold: 0.1 // 当元素10%进入视口时触发
      };

      // 创建观察器实例
      const observer = new IntersectionObserver((entries, observer) => {
        entries.forEach(entry => {
          if (entry.isIntersecting) { // 当元素进入视口
            const img = entry.target;
            // 替换src属性触发图片加载
            img.src = img.dataset.src;
            // 加载完成后移除观察
            img.addEventListener('load', () => {
              img.classList.add('loaded');
              observer.unobserve(img);
            });
          }
        });
      }, observerOptions);

      // 开始观察所有图片
      lazyImages.forEach(img => observer.observe(img));
    });
  </script>
</body>
</html>

代码解析:

  1. HTML结构

    • 使用data-src存储真实图片地址
    • 初始src为空或使用占位图
    • 通过CSS设置图片容器的占位样式
  2. IntersectionObserver配置

    • rootMargin: '0px 0px 200px 0px':提前200px触发加载
    • threshold: 0.1:当元素10%进入视口时触发
  3. 加载流程

    • 检测到元素进入视口时替换src属性
    • 图片加载完成后移除观察器

方案2:传统滚动监听方案(兼容旧浏览器)

html 复制代码
<script>
  // 节流函数:避免频繁触发滚动事件
  function throttle(func, delay = 200) {
    let lastCall = 0;
    return (...args) => {
      const now = new Date().getTime();
      if (now - lastCall < delay) return;
      lastCall = now;
      func.apply(this, args);
    }
  }

  // 判断元素是否进入视口
  function isInViewport(element) {
    const rect = element.getBoundingClientRect();
    return (
      rect.top <= window.innerHeight * 1.5 && // 提前1.5屏加载
      rect.bottom >= 0
    );
  }

  // 加载可见图片
  function lazyLoad() {
    const images = document.querySelectorAll('.lazy-img[data-src]');
    images.forEach(img => {
      if (isInViewport(img)) {
        img.src = img.dataset.src;
        img.removeAttribute('data-src');
      }
    });
  }

  // 初始化
  document.addEventListener('DOMContentLoaded', lazyLoad);
  window.addEventListener('scroll', throttle(lazyLoad));
  window.addEventListener('resize', throttle(lazyLoad));
</script>

实践建议

  1. 占位符策略

    • 使用与图片比例一致的占位容器
    • 可添加低质量图片预览(LQIP)
  2. 性能优化

    • 优先使用WebP格式图片
    • 配合loading="lazy"原生属性(兼容性需注意)
    html 复制代码
    <img src="image.jpg" loading="lazy" alt="...">

实现效果对比

指标 IntersectionObserver 滚动监听
性能消耗 低(浏览器原生优化) 高(频繁触发)
代码复杂度 简单 复杂
浏览器兼容性 IE不支持(需polyfill) 全兼容
精确度 依赖计算逻辑

注意事项

  1. 首屏图片不要使用懒加载
  2. 对SEO关键内容谨慎使用
  3. 移动端注意rootMargin的合理设置
  4. 优先使用原生loading="lazy"(需考虑目标用户浏览器)
相关推荐
Warren981 小时前
Lua 脚本在 Redis 中的应用
java·前端·网络·vue.js·redis·junit·lua
mCell2 小时前
JavaScript 运行机制详解:再谈 Event Loop
前端·javascript·浏览器
独行soc5 小时前
2025年渗透测试面试题总结-18(题目+回答)
android·python·科技·面试·职场和发展·渗透测试
艾伦~耶格尔5 小时前
【数据结构进阶】
java·开发语言·数据结构·学习·面试
帧栈6 小时前
开发避坑指南(27):Vue3中高效安全修改列表元素属性的方法
前端·vue.js
max5006006 小时前
基于桥梁三维模型的无人机检测路径规划系统设计与实现
前端·javascript·python·算法·无人机·easyui
excel6 小时前
使用函数式封装绘制科赫雪花(Koch Snowflake)
前端
愿天堂没有C++7 小时前
剑指offer第2版——面试题4:二维数组中的查找
c++·面试
萌萌哒草头将军7 小时前
Node.js v24.6.0 新功能速览 🚀🚀🚀
前端·javascript·node.js
持久的棒棒君8 小时前
启动electron桌面项目控制台输出中文时乱码解决
前端·javascript·electron