这是我参与更文挑战的第1天,活动详情查看: 更文挑战
前情
项目中需要对一系列图片实现预览功能,经过调研,在github上找到一个React库react-viewer,非常简单易用,完美实现需求。
github地址:github.com/infeng/reac…
实现效果示例:
核心代码如下,参数images为传入图片的列表。
class PicViewer extends PureComponent {
constructor(props) {
super(props);
this.state = {
visible: false,
images: [],
};
}
closeViewer = () => {
this.setState({ visible: false });
};
render() {
const { visible, images } = this.state;
return (
<div>
<Viewer
visible={visible}
onClose={this.closeViewer}
images={images}
downloadable
noImgDetails
zoomable={false}
zoomSpeed={0.3}
scalable={false}
loop={false}
showTotal={false}
noNavbar
customToolbar={toolbars =>
toolbars.filter(item => ['rotateLeft', 'rotateRight', 'download'].includes(item.key))
}
/>
</div>
);
}
}
问题
但是后来发现,随着业务场景的丰富,images可能会非常大,多则由几百张图片,如果把几百张图片一次性传给images,则会导致批量的往服务器发送几百个获取图片的请求,给服务器造成的压力巨大。
解决思路
我想到的解决办法是去实现懒加载功能。尽管图片数量很大,但我控制初始是每次传入该组件中的图片总数固定,比如10张,这样最多只向服务器请求10张图片。
另外,随着不断翻阅图片,会通过onchange方法检测当前图片数目的变化。假设翻阅到了图片末尾,会去更新该组件的图片范围,慢慢的做扩展,控制每次图片加载的数目。
举个例子,比如有20张图片,编号1-20。
任意点击一张图片时,我会读该图片的前4张和后5张,共10张图片,并去加载它们。比如我点到了编号为10的图片,就会像预览组件传入6-15这10张图片。
翻阅图片时,会触发预览组件的函数,如果检测到图片到达了边界(比如6或15),会触发一次图片范围的扩展。具体扩展方式为,若为左下标,则左边界往左更新1,预览组件图片范围变成1-15,若为右下标,则右边界往右更新5,预览组件图片范围变成6-20。
这样可以很流畅的达成预览的效果,又不会对服务器带宽资源造成太多的浪费。
实现方式
经查阅文档,react-viewer组件提供了onChange方法,该方法将会用作图片移动的监测。
同时还用到的重要的属性有:
- activeIndex:当前显示的图片的索引(注意这个索引是在当前传入预览组件中的图片列表的索引,因此我在写代码时会注意到要动态更新这个的值)
- images:图片列表
直接上代码,我把原来的Viewer封装成了我自己的组件PictureViewer
import Viewer from 'react-viewer';
import 'react-photo-view/dist/index.css';
import React, { PureComponent } from 'react';
class PictureViewer extends PureComponent {
constructor(props) {
super(props);
this.state = {
start: 0, // 图片左边界
end: 0, // 图片右边界
cursorIndex: 0, // 组件内部指针
useCursorIndex: false, // 是否使用组件指针
};
}
// 初始范围,activeIndex为调用该组件的组件传入的
componentWillReceiveProps(nextProps) {
const { activeIndex, images } = nextProps;
const { useCursorIndex } = this.state;
if (!useCursorIndex && nextProps.visible) {
if (activeIndex - 4 > 0) {
this.setState({ start: activeIndex - 4 });
} else {
this.setState({ start: 0 });
}
if (activeIndex + 5 > images.length) {
this.setState({ end: images.length });
} else {
this.setState({ end: activeIndex + 5 });
}
}
}
// 关闭预览,closeViewer的函数也来自于外层组件
closeViewer = () => {
const { closeViewer } = this.props;
this.setState({ useCursorIndex: false, start: 0, end: 0 });
closeViewer();
};
// 更新边界
changePic = (activeImage, index) => {
const { images } = this.props;
const { start, end } = this.state;
if (index === end - start - 1 && index < images.length - 1) {
if (end + 5 < images.length) {
this.setState({ end: end + 5, cursorIndex: index, useCursorIndex: true });
} else {
this.setState({ end: images.length, cursorIndex: index, useCursorIndex: true });
}
}
if (index === 0 && start !== 0) {
if (start - 5 > 0) {
this.setState({ start: start - 5, cursorIndex: 5, useCursorIndex: true });
} else {
this.setState({ start: 0, cursorIndex: start, useCursorIndex: true });
}
}
};
render() {
const { images, visible, activeIndex } = this.props;
const { start, end, cursorIndex, useCursorIndex } = this.state;
const showImages = images.slice(start, end);
// 这几行可用于调试
// console.log(useCursorIndex);
// console.log(`cursorIndex:${cursorIndex}`);
// console.log(`activeIndex:${activeIndex}`);
// console.log(`start:${start}`);
// console.log(`end:${end}`);
return (
<div>
<Viewer
visible={visible && end > 0}
onClose={this.closeViewer}
activeIndex={useCursorIndex ? cursorIndex : activeIndex - start}
images={showImages}
downloadable
noImgDetails
zoomable={false}
scalable={false}
loop={false}
showTotal={false}
onChange={(activeImage, index) => this.changePic(activeImage, index)}
/>
</div>
);
}
}
接下来我围绕截图,具体阐释这个过程发生了什么。
首先,20张图片整整齐齐。
然后,我点击第一张图片。此时加载了1-5张图片,start=0,end=5,activeIndex=0,userCursorIndex=false
我不断移动向右按钮,图片组件正常轮播,当我翻到第5张时,奇迹出现了,一次性请求了6-10张图片。此时start=0,end=10,activeIndex=0,cursorIndex=4,userCursorIndex=true。
这里也解释了我为什么要引用变量userCursorIndex,因为在我更新图片组件的范围时,activeIndex不会更新,所以我要人为的告诉图片组件,此时的activeIndex应该是哪个值。
我继续向右移动按钮,效果和预期相符,当翻到第10张时,继续请求11-15张,这时候的图片边界为0-15,即start和end的值。当我继续翻到第15张时,继续请求16-20张,这时所有的图片都已经请求完毕。图片边界也已经变成0-20,不管我怎么翻阅,都不会再进行加载了。
最后,我来演示一下一个从中间请求的case。
我来点击第8张图片。发现这张图片的前4张和后4张也加载完毕。此时start=3,end=12,activeIndex=7,cursorIndex=0,userCursorIndex=false。
随着我向前翻阅到第3张,或者向后翻阅到第12张,都会触发边界更新。这里就不做演示了。