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

前言

图片懒加载是一种优化网页性能的技术,它允许在用户滚动到图片位置之前延迟加载图片。通过懒加载,可以在用户需要查看图片时才加载图片,避免了不必要的图片加载,从而提高了网页的加载速度和用户体验。

方案一

实现思路

在说明思路之前,先了解几个常见的视图属性。

  1. clientHeight:可视区域的高度,对应的也就是下图中的滚动区域。
  2. scrollTop:滚动条滚动的高度,它指的是内容区的顶部到可视区域顶部的距离。
  3. offsetTop:元素到offsetParent顶部的距离。
  4. offsetParent:距离元素最近的一个具有定位的祖宗元素(relative,absolute,fixed),若祖宗都不符合条件,offsetParent为body。

图解:

根据上面的图解可知,当图片的滚动条滚动的高度加上可视区域的高度大于当前的图片的offsetTop,那么说明图片正在进入可视区域。这个时候便可以加载当前图片。

第一步

模拟后台返回的图片url,遍历产生一个url集合,用于后面的懒加载使用。

js 复制代码
const imgUrls = (num = 10) => {
  const urls = [];
  for (let i = 0; i < num; i++) {
    const url = `https://robohash.org/${i}.png`;
    urls.push(url);
  }
  return urls;
};

第二步

遍历图片url集合,渲染100张loading图片,css部分省略。

html 复制代码
    <div className={styles['box-one']} ref={scrollRef}>
      {imgUrls(100).map((item) => {
        return <img data-src={item} key={item} src={loadingUrl} alt="" />;
      })}
    </div>

效果预览

第三步

监听容器的滚动事件,当容器滚动时计算容器的高度加上滚动条的高度大于当前图片的offsetTop时加载当前的图片。完整代码如下:

tsx 复制代码
import loadingUrl from '@/assets/imgs/loading.jpg';
import { useEffect, useRef } from 'react';
import styles from '../index.less';

// 图片url
const imgUrls = (num = 10) => {
  const urls = [];
  for (let i = 0; i < num; i++) {
    const url = `https://robohash.org/${i}.png`;
    urls.push(url);
  }
  return urls;
};

const LazyLoading = () => {
  const scrollRef = useRef({} as any);

  // 滚动事件
  const changeScroll = () => {
    const clientHeight = scrollRef?.current.clientHeight; //可视区域高度
    const scrollTop = scrollRef?.current.scrollTop; //滚动条滚动高度
    const childNodes = scrollRef?.current.childNodes; // 获取所有图片集合

    for (let j = 0; j < childNodes.length; j++) {
      const element = childNodes[j];
      if (scrollTop + clientHeight > element.offsetTop) {
        element.src = element.getAttribute('data-src'); // 替换当前的src
      }
    }
  };

  useEffect(() => {
    changeScroll(); // 第一次渲染的时候替换loading图片
  }, []);

  return (
    <div className={styles['box-one']} ref={scrollRef} onScroll={changeScroll}>
      {imgUrls(100).map((item) => {
        return <img data-src={item} key={item} src={loadingUrl} alt="" />;
      })}
    </div>
  );
};

export default LazyLoading;

效果预览

方案二

实现思路

方案二的实现思路利用浏览器提供的 IntersectionObserver API实现。IntersectionObserver API提供了一种方便的方式来监视目标元素和其祖先元素或视窗之间的交叉状态变化。当目标元素进入或离开视口时,可以触发回调函数,进行相应的操作。它的原理是通过注册一个回调函数来观察特定元素的交叉状态变化,并在满足条件时执行相应的操作。

使用 IntersectionObserver API非常简单,可以通过创建一个 IntersectionObserver 实例,并传入回调函数和选项对象来实现。回调函数会在目标元素的交叉状态发生变化时被调用,并接收一个参数,包含有关交叉状态的信息。

实现完整代码

tsx 复制代码
import loadingUrl from '@/assets/imgs/loading.jpg';
import styles from '../index.less';
import React, { useRef, useEffect, useState } from 'react';
// 图片url
const imgUrls = (num = 10) => {
  const urls = [];
  for (let i = 0; i < num; i++) {
    const url = `https://robohash.org/${i}.png`;
    urls.push(url);
  }
  return urls;
};

const LazyLoadImage = ({ src, alt }) => {
  const [imageSrc, setImageSrc] = useState(loadingUrl);
  const imgRef = useRef(null as any);

  useEffect(() => {
    let observer: IntersectionObserver;
    if (imgRef.current) {
      // 创建IntersectionObserver实例
      observer = new IntersectionObserver(
        ([entry]) => {
          // 当图片进入可视区域时,设置图片地址进行加载
          if (entry.isIntersecting) {
            setImageSrc(src);
            observer.unobserve(imgRef.current);
          }
        },
        {
          rootMargin: '0px 0px 200px 0px', // 可视区域的上边距设置为200px
        },
      );
      observer.observe(imgRef.current); //开始观察目标元素
    }
    return () => {
      if (observer && observer.unobserve) {
        observer.unobserve(imgRef.current);
      }
    };
  }, [src]);

  return <img ref={imgRef} src={imageSrc} alt={alt} />;
};

const LazyLoading = () => {
  return (
    <div className={styles['box-two']}>
      {imgUrls(100).map((item) => {
        return <LazyLoadImage src={item} alt="lazy load image" />;
      })}
    </div>
  );
};

export default LazyLoading;

实现效果

⚠️注意事项

在初始化的时候,需要给imageSrc设置一个初始化的loading地址,如果没有的话,初始化的时候会加载多张图片。

方案三

实现思路

利用react的懒加载库react-lazyload,在使用之前需要先安装 yarn add react-lazyload,这里介绍几个它的常见属性:

  1. scrollContainer: 指定的滚动的区域,默认值是undefined,如果没有指定默认是窗口的视图作为滚动区域。
  2. offset: 元素距离视口顶部的距离,当达到这个距离时,元素将被加载。
  3. scroll: 是否监听滚动
  4. height: 渲染元素的占位符的高度。
  5. overflow : 如果溢出容器,延迟加载组件

代码实现

因为这里实现的图片懒加载是局部懒加载,所以需要指定 scrollContainerscrollContainer 的值DOM对象。在实现的过程中,同时需要设置overflow为true,以及height的值。

tsx 复制代码
import react, { useRef, useEffect } from 'react';
import LazyLoad from 'react-lazyload';
import styles from '../index.less';

// 图片url
const imgUrls = (num = 10) => {
  const urls = [];
  for (let i = 0; i < num; i++) {
    const url = `https://robohash.org/${i}.png`;
    urls.push(url);
  }
  return urls;
};

const LazyLoading = () => {
  const scrollRef = useRef({} as any);

  return (
    <div className={styles['box-three']} ref={scrollRef}>
      {imgUrls(100).map((item) => {
        return (
          <LazyLoad
            height={200}
            overflow={true}
            offset={0}
            key={item}
            scroll={true}
            scrollContainer={scrollRef.current} // DOM
          >
            <img src={item} alt="" />
          </LazyLoad>
        );
      })}
    </div>
  );
};

export default LazyLoading;

实现效果

相关推荐
m0_7482309432 分钟前
Redis 通用命令
前端·redis·bootstrap
一个 00 后的码农35 分钟前
25林业研究生复试面试问题汇总 林业专业知识问题很全! 林业复试全流程攻略 林业考研复试真题汇总
考研·面试·面试问题·考研复试·考研调剂·面试真题·林业考研
YaHuiLiang1 小时前
一切的根本都是前端“娱乐圈化”
前端·javascript·代码规范
界面开发小八哥1 小时前
可视化工具SciChart如何结合Deepseek快速创建一个React仪表板?
react.js·信息可视化·数据可视化·原生应用·scichart
ObjectX前端实验室2 小时前
个人网站开发记录-引流公众号 & 谷歌分析 & 谷歌广告 & GTM
前端·程序员·开源
CL_IN2 小时前
企业数据集成:实现高效调拨出库自动化
java·前端·自动化
WeiLai11123 小时前
面试基础--微服务架构:如何拆分微服务、数据一致性、服务调用
java·分布式·后端·微服务·中间件·面试·架构
浪九天4 小时前
Vue 不同大版本与 Node.js 版本匹配的详细参数
前端·vue.js·node.js
Java知识技术分享4 小时前
使用LangChain构建第一个ReAct Agent
python·react.js·ai·语言模型·langchain
qianmoQ4 小时前
第五章:工程化实践 - 第三节 - Tailwind CSS 大型项目最佳实践
前端·css