Taro实现VirtualList虚拟列表

摘要:
在使用Taro开发微信小程序时,需要加载长列表数据,在官网找了相关的VirtualList虚拟列表的组件,要么版本过低,要么使用不方便,官方也说有虚拟列表就是长列表加载,使用后发现性能不能达到满足,于是就参考网上的虚拟列表的思路开始自己做。

在使用Taro开发微信小程序时,需要加载长列表数据,在官网找了相关的VirtualList虚拟列表的组件,要么版本过低(项目中使用3.0.1版本),要么使用不方便(可能是自己没看懂的问题),官方也说有虚拟列表就是长列表加载,使用后发现性能不能达到满足,于是就参考网上的虚拟列表的思路开始自己做。

简单说下思路,设计思路是通过虚拟列表,只展示屏幕可视区域范围的数据,但在taro中效果还是有点卡顿,经大佬指点,渲染可视区域时,
渲染组件不再进行删除和新建,而是创建指定数量的组件展示,向下滚动时,将最上面的组件通过css修改样式显示的位置,
形成一种链条滚动的形式。

简化的思路图:

Taro实现VirtualList虚拟列表第1张

下面贴出主要的代码:

1 /**
2 * author: wang.p  2021-09-18
3 *
4 * description:  自定义虚拟列表
5 *
6 * */
7 
8 import React, {Component} from "react"
9 import {ScrollView, View} from '@tarojs/components'
10 import PropsType from 'prop-types';
11 import classnames from 'classnames';
12 import './virtual-list.scss';
13 
14 class VirtualList extends Component {
15 
16   static propTypes ={
17     className: PropsType.string, //样式名
18     rowCount: PropsType.number,  //渲染的行数
19     source: PropsType.array,  //数据源数组
20     rowHeight: PropsType.number,  //行高
21     scrollToIndex: PropsType.number, //跳转到指定的位置
22     getRowHeight: PropsType.func,  //动态行高
23     onScroll: PropsType.func, //滚动处罚事件
24     onRowRender: PropsType.func, //行渲染
25     onSrollTopRecommend: PropsType.func, //触发顶部样式事件   这个是本人项目中使用的,可以不用
26 }
27 
28   static defaultProps ={
29     rowCount: 20,
30 source: [],
31     rowHeight: 40
32 }
33 
34   state ={
35     rowCount: 20, //显示行数
36     scrollHeight: 0, //所有内容渲染的高度
37     scrollData: [],  //渲染可视区域数据的数组
38     scrollStyles: [], //样式数组
39     isCategoryToScroll: false, //是否是分类切换定位滚动
40     scrollToIndex: 0, //跳转到指定位置
41     compareHeight: 0  //触发渲染的高度差
42 }
43 
44   componentWillMount = () =>{
45     const {rowCount, source, rowHeight, getRowHeight} = this.props;
46     let scrollStyles =[];
47     let scrollData =[];
48     let scrollHeight = 0;
49     let compareHeight = 0;
50     source.forEach((item, idx) =>{
51       let styles = {position: 'absolute', left: 0, top: scrollHeight};
52 scrollStyles.push(styles);
53       let tempHeight = typeof getRowHeight === 'function' ?getRowHeight(idx, item) : rowHeight;
54       scrollHeight +=tempHeight;
55 });
56 
57     let showCount = source.length < rowCount ?source.length : rowCount;
58     for (let i = 0; i < showCount; i++) {
59 scrollData.push({sort: i, row: i})
60 }
61 
62     compareHeight = Math.floor(scrollStyles[showCount - 1].top / showCount) * 3;
63 
64     this.setState({scrollHeight, scrollData, scrollStyles, rowCount, compareHeight});
65 }
66 
67 
68   componentDidMount = () =>{
69 
70 }
71 
72   componentWillReceiveProps(nextProps: Readonly<P>, nextContext: any) {
73 
74     if (nextProps.scrollToIndex != this.state.scrollToIndex && this.state.scrollStyles.length > 0) {
75       let scrollToIndex = nextProps.scrollToIndex > this.state.scrollStyles.length ? this.state.scrollStyles.length - 1: nextProps.scrollToIndex;
76       let scrollTop = this.state.scrollStyles[scrollToIndex];
77       if(scrollTop) {
78         this.setState({scrollToIndex: nextProps.scrollToIndex, scrollTop: scrollTop.top , isCategoryToScroll: true});
79 }
80 }
81 }
82 
83 render() {
84     const {className, style } = this.props;
85     const {scrollHeight, scrollData, scrollStyles, scrollTop} = this.state;
86 
87     return <ScrollView className={classnames('self-virtual-list', className)}
88                        style={{...style}}
89                        scrollTop={scrollTop}
90                        scrollY={true}
91 scrollWithAnimation
92                        onScroll={this.onScroll.bind(this)}>
93       <View className={'self-virtual-list-body'} style={{height: scrollHeight}}>
94         {scrollData.length > 0 && scrollData.map((item, idx) =>{
95           return this.props.onRowRender(item, scrollStyles[item.row]);
96 })}
97       </View>
98     </ScrollView>
99 
100 
101 }
102 
103   currentScrollTop = 0;
104   prevScrollTop = 0;  //记录上次滚动的Scrolltop
105 
106   findMinOrMax = (data, isMax= false) =>{
107     if(isMax) {
108       return data.reduce((prev, next) =>{
109         if (prev.row <next.row) {
110           returnnext;
111         } else{
112           returnprev;
113 }
114 })
115     } else{
116       return data.reduce((prev, next) =>{
117         if (prev.row <next.row) {
118           returnprev;
119         } else{
120           returnnext;
121 }
122 })
123 }
124 }
125 
126   /**
127 * 滚动事件,计算渲染菜品的数据
128 * 滚动到顶部时,如果顶部有推荐菜品就展示出来,如果上拉滚动,就隐藏推荐菜品
129 * */
130   onScroll = (event) =>{
131     let scrollY =event.detail.deltaY;
132     const eventScrollTop = event ? event.detail.scrollTop : this.state.scrollTop;
133     const {scrollStyles, rowCount, compareHeight, scrollData, isCategoryToScroll} = this.state;
134 
135     if (Math.abs(this.currentScrollTop - eventScrollTop) > compareHeight || eventScrollTop <= scrollStyles[3].top || eventScrollTop >= scrollStyles[scrollStyles.length - 5].top) {
136       //查询出当前scrollTop在那个范围
137       this.currentScrollTop =eventScrollTop;
138       let scrollIndex = 0;
139       for(let i=1;i<scrollStyles.length;i++) {
140         if (scrollStyles[i - 1].top <= eventScrollTop && scrollStyles[i].top >eventScrollTop) {
141           scrollIndex =i;
142           break;
143 }
144 }
145       //计算出渲染范围的最小下标和最大下标
146       let minIndex = scrollIndex - parseInt(Math.floor(rowCount / 2.0));
147       if (minIndex < 0) {
148         minIndex = 0;
149 }
150       let maxIndex = minIndex +rowCount;
151       if (maxIndex > scrollStyles.length - 1) {
152         maxIndex = scrollStyles.length - 1;
153 }
154       //找出当前显示的数据范围最小值和最大值
155       let minData = this.findMinOrMax(scrollData);
156       let maxData = this.findMinOrMax(scrollData, true);
157       let newScrollData =[...scrollData];
158       if (minIndex >minData.row) {
159         //向下滑动渲染, 找出最小值,替换成最大值,循环进行替换
160         let cycle = minIndex -minData.row;
161         for (let i = 0; i < cycle; i++) {
162           minData = this.findMinOrMax(scrollData);
163           maxData = this.findMinOrMax(scrollData, true);
164 
165           scrollData[minData.sort]['row'] = maxData.row + 1;
166 }
167 
168         this.setState({scrollData: newScrollData, isCategoryToScroll: false});
169       } else{
170         //向上滑动渲染
171         let cycle = minData.row -minIndex;
172         for (let i = 0; i < cycle; i++) {
173           minData = this.findMinOrMax(scrollData);
174           maxData = this.findMinOrMax(scrollData, true);
175 
176           scrollData[maxData.sort]['row'] = minData.row - 1;
177 }
178         this.setState({scrollData: newScrollData, isCategoryToScroll: false});
179 }
180 
181 }
182 
183     let scycelScroll = compareHeight / 3;
184     //滚动一定距离,就触发外部事件
185     if (!isCategoryToScroll && Math.abs(this.prevScrollTop - eventScrollTop) > scycelScroll && this.props.onScroll) {
186       this.prevScrollTop =eventScrollTop;
187       let scrollIndex = 0;
188       for(let i=1;i<scrollStyles.length;i++) {
189         if (scrollStyles[i - 1].top <= eventScrollTop && scrollStyles[i].top >eventScrollTop) {
190           scrollIndex =i;
191           break;
192 }
193 }
194       this.props.onScroll(scrollIndex);
195 }
196 
197     //处理顶部隐藏的组件
198     if (this.props.onSrollTopRecommend) {
199       if (scrollY > 0) {
200         //下拉
201         if (event.detail.scrollTop <=scycelScroll) {
202           //展开推荐菜品
203           this.props.onSrollTopRecommend && this.props.onSrollTopRecommend(true);
204 }
205       } else{
206         //上拉
207         if (event.detail.scrollTop >scycelScroll) {
208           //触发收起推荐菜品
209           this.props.onSrollTopRecommend && this.props.onSrollTopRecommend(false);
210 }
211 }
212 }
213 
214 }
215 
216 
217 }
218 
219 
220 export default VirtualList

在项目中的效果图:

Taro实现VirtualList虚拟列表第2张Taro实现VirtualList虚拟列表第3张

使用方法说明:引入组件,添加对应的方法

<VirtualList className={'category-virtual'}
            width={width}
            height={height}
            source={source}
            rowCount={20}
            getRowHeight={this.getRowHeight}
            scrollToIndex={scrollIndex}
            onRowRender={this.onRowRender}
            onScroll={this.onScroll}
/>
/**
* 获取行高
* @idx 数据源下标
* @value 数据
*/getRowHeight = (idx, value) =>{
//这里可以通过数据源的下标idx,来返回高度

return 100; //高度可以通过函数来计算
}
/**
* @data 数据{sort: 渲染的行数的顺序, row: 渲染数据源的下标}
* @style: 样式
* */onRowRender = (data, style) =>{
const {sort, row} =data;
const {source} = this.state;

return <View key={sort} style={style}>...
</View>
}
/**
* 通过滚动
* @currentIndex 分类数组下标
* */onScroll = (currentIndex) =>{
//通过滚动的触发事件执行某些方法
}

下面提供代码下载路径:

链接:https://pan.baidu.com/s/1kW0w1D03N72uCE3S9Vy2zg
提取码:460i

免责声明:文章转载自《Taro实现VirtualList虚拟列表》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇06-OpenLDAP密码策略Charles 手机抓包HTTPS设置以及证书安装下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

e.pageX、e.clientX、e.screenX、e.offsetX的区别以及元素的一些CSS属性

e.pageX,e.pageY:返回的值是相对于文档的定位,文档的左上角为(0,0),向右为正,向下为正,IE不支持;e.clientX,e.clientY:返回的值是相对于屏幕可见区域的坐标,如果页面有滚动条,呗滚动条隐藏的那部分不进行计算,也可以说是相对于屏幕的坐标,但是不计算上方的工具栏;e.screenX,e.screenY:返回的是相对于屏幕的坐...

微信小程序-自定义下拉刷新

要实现微信小程序上拉刷新与下拉加载更多 微信给出的接口不怎么友好,最终想实现效果类似QQ手机版 ,一共3种下拉刷新状态变化,文字+图片+背景颜色 最终实现后的效果(这里提示有个不同点就是,自定义了导航条,并且下拉的时候,自定义导航条必须固定) 小程序实现下拉加载2种方式: 1. 简单粗暴,直接开启enablePullDownRefresh,开启全局下...

底部浮动

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="...

JS 滚动条滚动到指定元素触发

JS 版 <!DOCTYPE html> <html> <head> <style type="text/css">#showIt { 200px; height: 200px; background-color: red;...

原生JS 和 JQ 获取滚动条的高度,以及距离顶部的高度

JQ:相对比较简便 获取浏览器显示区域(可视区域)的高度 : $(window).height(); 获取浏览器显示区域(可视区域)的宽度 : $(window).width(); 获取页面的文档高度 $(document).height(); 获取页面的文档宽度 : $(document).width(); 浏览器当前窗口...

jquery 鼠标滚轮事件 插件 Mousewheel

jquery插件默认是不支持鼠标中轮滚轮事件的,现在我们可以用于添加跨浏览器的鼠标滚轮支持可以使用jquery的Mousewheel插件。 使用mousewheel事件有以下两种方式: 使用mousewheel和unmousewheel事件函数; 使用经典的bind和unbind函数。 JavaScript Code复制内容到剪贴板 $('div.m...