前言

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

方法一

实现思路

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

  1. clientHeight:元素的像素高度,包含元素的高度+内边距,不包含水平滚动条,边框和外边距。

  2. scrollTop:滚动条滚动的高度,它指的是内容区的顶部到可视区域顶部的距离。

  3. offsetParent:距离元素最近的一个具有定位的祖宗元素(relative,absolute,fixed),若祖宗都不符合条件,offsetParent为body。

  4. offsetTop:元素到offsetParent顶部的距离。

    img

滚动条的滚动高度:表示滚动条已经向下滚动的距离,即页面顶部到可视区域顶部的距离。

可视区域的高度:表示当前浏览器窗口或容器中可见的部分的高度。

当前图片的 offsetTop:表示图片顶部相对于文档顶部的距离。

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

第一步

模拟后台返回的图片url,遍历产生一个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;
};

第二步

遍历图片url集合,渲染loading图片

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

image.png

第三步

监听容器的滚动事件,当容器滚动时计算容器的高度加上滚动条的高度大于当前图片的offsetTop时加载当前的图片。

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;

image.png

方法二

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

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

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
//rootMargin 是 IntersectionObserver 的配置项之一,用于指定根元素边界框的外边距,以便扩大或缩小视口的大小
},
);
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;

2024-01-08 16.17.31.gif

注意

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

方法三

利用react的懒加载库react-lazyload,这里介绍几个它的常见属性:

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

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

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;