MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

React 图片懒加载与性能优化方案

2022-10-015.3k 阅读

React 图片懒加载概述

在前端开发中,图片是占据页面资源的重要元素。随着页面复杂度的提升以及图片数量的增多,图片加载对性能的影响愈发显著。图片懒加载作为一种优化手段,能够显著改善页面性能,特别是在用户体验和资源利用方面。

在 React 应用中,懒加载图片可以延迟图片的加载时机,直到图片进入浏览器的可视区域,才进行加载。这样做的好处是,当页面首次渲染时,不会一次性加载所有图片,从而加快页面的初始渲染速度,减少不必要的网络请求,节省用户流量,尤其是在移动设备上,这种优化效果更为明显。

React 中实现图片懒加载的常见方式

使用 Intersection Observer API

Intersection Observer API 是现代浏览器提供的一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport)交叉变化情况的方法。在 React 中,我们可以利用它来实现图片的懒加载。

首先,创建一个自定义的 React 组件,例如 LazyImage

import React, { useState, useRef, useEffect } from 'react';

const LazyImage = ({ src, alt }) => {
  const imgRef = useRef(null);
  const [isLoaded, setIsLoaded] = useState(false);

  useEffect(() => {
    const observer = new IntersectionObserver((entries) => {
      if (entries[0].isIntersecting) {
        setIsLoaded(true);
        observer.unobserve(imgRef.current);
      }
    });

    if (imgRef.current) {
      observer.observe(imgRef.current);
    }

    return () => {
      if (imgRef.current) {
        observer.unobserve(imgRef.current);
      }
    };
  }, []);

  return (
    <div>
      {isLoaded ? (
        <img src={src} alt={alt} ref={imgRef} />
      ) : (
        <div>Loading...</div>
      )}
    </div>
  );
};

export default LazyImage;

在上述代码中:

  1. 使用 useRef 创建一个 imgRef 来引用图片元素。
  2. useState 用于跟踪图片是否已加载,初始值为 false
  3. useEffect 钩子函数在组件挂载时执行。创建一个 IntersectionObserver 实例,当观察的元素(即图片)进入视口时(entries[0].isIntersectingtrue),设置 isLoadedtrue 并停止观察。
  4. 组件返回时,如果图片已加载,显示图片;否则显示 “Loading...”。

使用第三方库 react - lazyload

react - lazyload 是一个专门用于 React 的图片懒加载库,使用起来相对简洁。

首先,安装该库:

npm install react - lazyload

然后,在组件中使用:

import React from'react';
import LazyLoad from'react - lazyload';

const MyComponent = () => {
  return (
    <LazyLoad>
      <img src="your - image - url.jpg" alt="example" />
    </LazyLoad>
  );
};

export default MyComponent;

react - lazyload 库会自动处理图片的懒加载逻辑,当图片进入视口时进行加载。它还提供了一些额外的配置选项,例如:

<LazyLoad
  offset={100}
  placeholder={<div>Loading...</div>}
>
  <img src="your - image - url.jpg" alt="example" />
</LazyLoad>
  • offset 表示在距离视口多远时开始加载图片,单位是像素。
  • placeholder 可以设置图片加载前显示的占位元素。

图片懒加载与性能优化的深入分析

减少初始渲染时间

当页面包含大量图片时,如果所有图片都在页面初始渲染时加载,会导致渲染时间大幅增加。通过懒加载,只有当图片即将进入视口时才加载,大大减少了初始渲染阶段需要处理的资源数量,使得页面能够更快地呈现给用户。例如,一个电商产品列表页面,可能展示数十个商品图片,如果不使用懒加载,用户可能需要等待较长时间才能看到页面内容;而采用懒加载后,用户几乎瞬间就能看到页面框架,随着滚动逐渐加载图片,提升了用户体验。

节省网络资源

懒加载避免了用户未浏览区域图片的不必要加载。假设一个长页面有许多图片,用户可能只浏览了前半部分,那么后半部分图片如果采用懒加载,就不会产生网络请求,从而节省了用户的流量,也减轻了服务器的负载。对于服务器来说,减少不必要的图片请求意味着可以处理更多其他重要的业务请求,提升整体的服务性能。

避免回流与重绘

在图片加载过程中,如果图片的尺寸、位置等发生变化,可能会导致页面的回流(reflow)和重绘(repaint)。回流会重新计算元素的布局,重绘则是重新绘制元素的外观。这两个操作都会消耗性能。通过懒加载,图片在合适的时机加载,并且可以提前设置好图片的尺寸等属性,避免在图片加载过程中引起回流和重绘。例如:

<LazyImage
  src="image.jpg"
  alt="example"
  style={{ width: '200px', height: '150px' }}
/>

提前设置好图片的宽度和高度,当图片加载时,不会因为尺寸变化而导致页面的回流和重绘。

结合 React.lazy 和 Suspense 实现图片懒加载

React.lazySuspense 主要用于代码分割和懒加载组件,但我们也可以巧妙地利用它们来实现图片的懒加载。

首先,创建一个专门用于加载图片的组件,例如 ImageLoader

import React, { Suspense } from'react';

const ImageLoader = React.lazy(() => import('./ImageComponent'));

const MyPage = () => {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <ImageLoader />
      </Suspense>
    </div>
  );
};

export default MyPage;

ImageComponent 中,处理图片的实际加载逻辑:

import React from'react';

const ImageComponent = () => {
  return <img src="your - image - url.jpg" alt="example" />;
};

export default ImageComponent;

在这个例子中,React.lazy 延迟加载 ImageComponentSuspense 组件在 ImageComponent 加载过程中显示 “Loading...”。这种方式虽然不是直接针对图片的懒加载,但通过延迟加载包含图片的组件,达到了类似的效果。在大型应用中,这种方式可以将图片相关的代码分割出来,进一步优化性能。

图片懒加载在不同场景下的应用

列表页图片懒加载

在电商产品列表、文章列表等场景中,通常会展示大量图片。以电商产品列表为例:

import React from'react';
import LazyImage from './LazyImage';

const ProductList = ({ products }) => {
  return (
    <ul>
      {products.map((product) => (
        <li key={product.id}>
          <LazyImage src={product.imageUrl} alt={product.title} />
          <p>{product.title}</p>
        </li>
      ))}
    </ul>
  );
};

export default ProductList;

在这个列表组件中,每个产品图片都通过 LazyImage 组件进行懒加载,有效提升了列表页面的加载性能。

无限滚动页面的图片懒加载

对于无限滚动的页面,懒加载图片尤为重要。当用户滚动到页面底部时,新的数据和图片会不断加载。结合 Intersection Observer API 实现无限滚动和图片懒加载:

import React, { useState, useRef, useEffect } from'react';
import LazyImage from './LazyImage';

const InfiniteScroll = () => {
  const [items, setItems] = useState([]);
  const [page, setPage] = useState(1);
  const observerRef = useRef(null);

  useEffect(() => {
    const fetchData = async () => {
      const response = await fetch(`https://api.example.com/data?page=${page}`);
      const data = await response.json();
      setItems([...items, ...data]);
    };

    fetchData();
  }, [page]);

  useEffect(() => {
    const observer = new IntersectionObserver((entries) => {
      if (entries[0].isIntersecting) {
        setPage((prevPage) => prevPage + 1);
      }
    });

    if (observerRef.current) {
      observer.observe(observerRef.current);
    }

    return () => {
      if (observerRef.current) {
        observer.unobserve(observerRef.current);
      }
    };
  }, []);

  return (
    <div>
      {items.map((item) => (
        <div key={item.id}>
          <LazyImage src={item.imageUrl} alt={item.title} />
          <p>{item.title}</p>
        </div>
      ))}
      <div ref={observerRef}></div>
    </div>
  );
};

export default InfiniteScroll;

在上述代码中,Intersection Observer 不仅用于图片懒加载,还用于触发无限滚动的加载逻辑。当底部的占位元素进入视口时,加载下一页的数据,同时新数据中的图片也通过 LazyImage 进行懒加载。

图片懒加载的性能监测与优化策略调整

为了确保图片懒加载真正提升性能,需要对其进行性能监测。可以使用浏览器的开发者工具,如 Chrome DevTools 中的 Performance 面板。

  1. 监测指标

    • 首次内容绘制时间(First Contentful Paint, FCP):这是页面开始显示文本、图像、SVG 等任何部分的时间。通过懒加载减少初始渲染图片数量,FCP 时间应该会缩短。
    • 最大内容绘制时间(Largest Contentful Paint, LCP):测量视口内最大的内容元素加载并呈现在屏幕上的时间。优化图片懒加载,确保重要图片及时加载,有助于降低 LCP 时间。
    • 累积布局偏移(Cumulative Layout Shift, CLS):监测页面布局的稳定性。通过提前设置图片尺寸等属性,避免图片加载导致的布局偏移,降低 CLS 值。
  2. 优化策略调整

    • 调整懒加载时机:如果发现图片加载过早或过晚,可以调整 Intersection Observer 的阈值或 react - lazyloadoffset 参数。例如,如果图片在距离视口很远时就加载,导致不必要的资源浪费,可以减小 offset 值。
    • 图片质量与尺寸优化:即使采用懒加载,如果图片本身质量过高、尺寸过大,仍然会影响性能。可以使用工具对图片进行压缩,根据实际显示需求调整图片尺寸。例如,对于列表页的小图片,适当降低分辨率不会影响用户体验,但能显著减少图片大小。
    • 预加载关键图片:对于页面首屏或用户关注度较高的图片,可以在页面初始化时进行预加载。在 React 中,可以使用 useEffect 钩子函数结合 fetchXMLHttpRequest 对关键图片进行预加载:
import React, { useEffect } from'react';

const MyComponent = () => {
  useEffect(() => {
    const preloadImage = new Image();
    preloadImage.src = 'critical - image - url.jpg';
  }, []);

  return (
    <div>
      {/* 页面内容 */}
    </div>
  );
};

export default MyComponent;

这样,当图片需要显示时,已经在缓存中,可以快速呈现。

处理图片懒加载中的错误情况

在图片懒加载过程中,可能会出现各种错误,例如图片链接错误、网络故障等。处理这些错误情况对于保证用户体验至关重要。

  1. 使用 onError 事件:在 img 标签上可以添加 onError 事件处理函数,当图片加载失败时执行相应操作。
import React, { useState } from'react';

const LazyImage = ({ src, alt }) => {
  const [error, setError] = useState(false);

  const handleError = () => {
    setError(true);
  };

  return (
    <div>
      {error? (
        <p>Failed to load image</p>
      ) : (
        <img src={src} alt={alt} onError={handleError} />
      )}
    </div>
  );
};

export default LazyImage;

在上述代码中,当图片加载失败时,handleError 函数会被调用,设置 error 状态为 true,从而显示错误提示信息。

  1. 重试机制:对于因网络故障等原因导致的图片加载失败,可以添加重试机制。
import React, { useState, useEffect } from'react';

const LazyImage = ({ src, alt }) => {
  const [error, setError] = useState(false);
  const [retryCount, setRetryCount] = useState(0);
  const maxRetries = 3;

  const handleError = () => {
    if (retryCount < maxRetries) {
      setRetryCount((prevCount) => prevCount + 1);
    } else {
      setError(true);
    }
  };

  useEffect(() => {
    if (retryCount > 0) {
      const timer = setTimeout(() => {
        const img = new Image();
        img.src = src;
        img.onload = () => {
          setError(false);
          setRetryCount(0);
        };
        img.onerror = handleError;
      }, 1000 * retryCount);

      return () => clearTimeout(timer);
    }
  }, [retryCount, src]);

  return (
    <div>
      {error? (
        <p>Failed to load image after {maxRetries} retries</p>
      ) : (
        <img src={src} alt={alt} onError={handleError} />
      )}
    </div>
  );
};

export default LazyImage;

在这个改进的代码中,当图片加载失败时,会根据 retryCount 判断是否达到最大重试次数。如果未达到,会在一定时间后(随着重试次数增加,时间间隔逐渐延长)重试加载图片。如果达到最大重试次数仍失败,则显示错误信息。

图片懒加载与 SEO 的关系

虽然图片懒加载主要是为了提升性能和用户体验,但它对 SEO 也有一定的影响。

  1. 搜索引擎爬虫:搜索引擎爬虫在抓取页面内容时,通常不会像真实用户一样滚动页面来触发懒加载。这就可能导致部分懒加载的图片无法被爬虫抓取到,从而影响页面在搜索引擎中的展示。为了解决这个问题,可以采用服务器端渲染(SSR)或静态站点生成(SSG)的方式。在 SSR 或 SSG 过程中,图片可以在服务器端被渲染并加载,确保爬虫能够获取到完整的页面内容。例如,使用 Next.js 或 Gatsby 等框架进行 SSR 或 SSG 开发,配置好图片加载策略,使得懒加载图片在生成页面时就被处理好,以便搜索引擎爬虫能够正常抓取。
  2. 页面加载速度与 SEO:图片懒加载能够提升页面的加载速度,而页面加载速度是搜索引擎排名的一个重要因素。快速加载的页面更受搜索引擎青睐,因为它提供了更好的用户体验。通过合理的图片懒加载优化,减少初始渲染时间,提高页面的整体性能,有助于提升页面在搜索引擎中的排名。

响应式图片与懒加载的结合

在当今多设备浏览的时代,响应式图片是前端开发的重要组成部分。将响应式图片与懒加载结合,可以进一步提升性能。

  1. 使用 srcsetsizes 属性srcset 属性允许我们为不同设备像素比和屏幕宽度提供不同的图片源,sizes 属性则定义了不同屏幕宽度下图片的布局宽度。结合懒加载,例如:
import React, { useState, useRef, useEffect } from'react';

const ResponsiveLazyImage = ({ sources, alt }) => {
  const imgRef = useRef(null);
  const [isLoaded, setIsLoaded] = useState(false);

  useEffect(() => {
    const observer = new IntersectionObserver((entries) => {
      if (entries[0].isIntersecting) {
        setIsLoaded(true);
        observer.unobserve(imgRef.current);
      }
    });

    if (imgRef.current) {
      observer.observe(imgRef.current);
    }

    return () => {
      if (imgRef.current) {
        observer.unobserve(imgRef.current);
      }
    };
  }, []);

  return (
    <div>
      {isLoaded? (
        <img
          srcSet={sources.map((source) => `${source.src} ${source.media}`).join(', ')}
          sizes={sources.map((source) => `(max - width: ${source.breakpoint}) ${source.layoutWidth}`).join(', ')}
          alt={alt}
          ref={imgRef}
        />
      ) : (
        <div>Loading...</div>
      )}
    </div>
  );
};

export default ResponsiveLazyImage;

在上述代码中,sources 是一个数组,包含不同分辨率图片的 srcmedia(设备像素比等条件)以及 breakpointlayoutWidth。当图片进入视口时,浏览器会根据设备的实际情况选择最合适的图片进行加载。

  1. 优化图片资源:根据不同设备的特点,优化图片资源。对于移动设备,可以提供更小尺寸、更低分辨率的图片,同时结合懒加载,进一步节省流量和提升加载速度。例如,对于视网膜屏幕(高像素比)的设备,提供高分辨率图片,但也要注意控制图片质量,避免过大的文件大小。

总结图片懒加载在 React 应用中的重要性与应用要点

图片懒加载在 React 应用中是提升性能、优化用户体验的关键技术。从使用 Intersection Observer API 或第三方库实现基本的懒加载,到结合 React 的其他特性如 React.lazySuspense,以及在不同场景(列表页、无限滚动页等)中的应用,都展现了其灵活性和强大功能。

在实际应用中,要注意处理图片加载错误、结合 SEO 需求、与响应式图片配合使用等要点。通过合理的性能监测和优化策略调整,确保图片懒加载在提升页面性能的同时,不引入新的问题。通过不断优化图片懒加载技术,为用户提供更加流畅、快速的 React 应用体验。