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

简介

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

懒加载有什么好处?

  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"(需考虑目标用户浏览器)
相关推荐
2501_915373883 小时前
Vue 3零基础入门:从环境搭建到第一个组件
前端·javascript·vue.js
沙振宇6 小时前
【Web】使用Vue3开发鸿蒙的HelloWorld!
前端·华为·harmonyos
运维@小兵7 小时前
vue开发用户注册功能
前端·javascript·vue.js
蓝婷儿7 小时前
前端面试每日三题 - Day 30
前端·面试·职场和发展
oMMh7 小时前
使用C# ASP.NET创建一个可以由服务端推送信息至客户端的WEB应用(2)
前端·c#·asp.net
一口一个橘子7 小时前
[ctfshow web入门] web69
前端·web安全·网络安全
读心悦8 小时前
CSS:盒子阴影与渐变完全解析:从基础语法到创意应用
前端·css
湛海不过深蓝10 小时前
【ts】defineProps数组的类型声明
前端·javascript·vue.js
layman052810 小时前
vue 中的数据代理
前端·javascript·vue.js