UNPKG

8.27 kBJavaScriptView Raw
1/**
2 * 处理滚动加载逻辑
3 */
4import React, { Component } from 'react';
5import PropTypes from 'prop-types';
6import {throttle} from './util';
7import CONFIG from './config';
8
9export default class InfiniteScroll extends Component {
10 static propTypes = {
11 children: PropTypes.node.isRequired,
12 element: PropTypes.node,
13 ref: PropTypes.func,
14 getScrollParent: PropTypes.func,
15 treeList: PropTypes.array,
16 handleTreeListChange: PropTypes.func
17 };
18
19 static defaultProps = {
20 element: 'div',
21 ref: null,
22 getScrollParent: null,
23 treeList: [],
24 handleTreeListChange: () => {}
25 };
26
27 constructor(props) {
28 super(props);
29
30 //默认显示20条,rowsInView根据定高算的。在非固定高下,这个只是一个大概的值。
31 this.rowsInView = CONFIG.defaultRowsInView;
32 //一维数组
33 this.treeList = props.treeList;
34 //一次加载多少数据
35 this.loadCount = CONFIG.loadBuffer ? this.rowsInView + CONFIG.loadBuffer * 2 : 16;
36 //可视区第一条数据的 index
37 this.currentIndex = 0;
38 this.startIndex = this.currentIndex; //数据开始位置
39 this.endIndex = this.currentIndex + this.loadCount; //数据结束位置
40 }
41
42 componentDidMount() {
43 this.options = this.eventListenerOptions();
44 this.attachScrollListener();
45 }
46
47 componentWillReceiveProps(nextProps){
48 let {treeList:newTreeList} = nextProps;
49 let {treeList:oldTreeList} = this.props;
50 if(newTreeList !== oldTreeList) {
51 this.treeList = newTreeList;
52 this.handleScrollY();
53 }
54 }
55
56 componentWillUnmount() {
57 this.detachScrollListener();
58 this.detachMousewheelListener();
59 }
60
61 isPassiveSupported() {
62 let passive = false;
63
64 const testOptions = {
65 get passive() {
66 passive = true;
67 }
68 };
69
70 try {
71 document.addEventListener('test', null, testOptions);
72 document.removeEventListener('test', null, testOptions);
73 } catch (e) {
74 // ignore
75 }
76 return passive;
77 }
78
79 eventListenerOptions = () => {
80 let options = this.props.useCapture;
81
82 if (this.isPassiveSupported()) {
83 options = {
84 useCapture: this.props.useCapture,
85 passive: true
86 };
87 }
88 return options;
89 }
90
91 /**
92 * 解除mousewheel事件监听
93 */
94 detachMousewheelListener() {
95 let scrollEl = window;
96 if (this.props.useWindow === false) {
97 scrollEl = this.scrollComponent.parentNode;
98 }
99
100 scrollEl.removeEventListener(
101 'mousewheel',
102 this.mousewheelListener,
103 this.options ? this.options : this.props.useCapture
104 );
105 }
106 /**
107 * 解除scroll事件监听
108 */
109 detachScrollListener() {
110 let scrollEl = window;
111 if (this.props.useWindow === false) {
112 scrollEl = this.getParentElement(this.scrollComponent);
113 }
114
115 scrollEl.removeEventListener(
116 'scroll',
117 this.scrollListener,
118 this.options ? this.options : this.props.useCapture
119 );
120 scrollEl.removeEventListener(
121 'resize',
122 this.scrollListener,
123 this.options ? this.options : this.props.useCapture
124 );
125 }
126 /**
127 * 获取父组件(用户自定义父组件或者当前dom的parentNode)
128 * @param {*} el
129 */
130 getParentElement(el) {
131 const scrollParent =
132 this.props.getScrollParent && this.props.getScrollParent();
133 if (scrollParent != null) {
134 return scrollParent;
135 }
136 return el && el.parentNode;
137 }
138
139 filterProps(props) {
140 return props;
141 }
142 /**
143 * 绑定scroll事件
144 */
145 attachScrollListener() {
146 const { store } = this.props;
147 const parentElement = this.getParentElement(this.scrollComponent);
148 if (!parentElement) {
149 return;
150 }
151 let scrollEl = parentElement;
152 let scrollY = scrollEl && scrollEl.clientHeight;
153
154 let rowHeight = store.getState().rowHeight;
155 //默认显示20条,rowsInView根据定高算的。在非固定高下,这个只是一个大概的值。
156 this.rowsInView = scrollY ? Math.floor(scrollY / rowHeight) : CONFIG.defaultRowsInView;
157
158 scrollEl.addEventListener(
159 'scroll',
160 this.scrollListener,
161 this.options ? this.options : this.props.useCapture
162 );
163 scrollEl.addEventListener(
164 'resize',
165 this.scrollListener,
166 this.options ? this.options : this.props.useCapture
167 );
168 }
169
170 mousewheelListener = (e) => {
171 // Prevents Chrome hangups
172 // See: https://stackoverflow.com/questions/47524205/random-high-content-download-time-in-chrome/47684257#47684257
173 if (e.deltaY === 1 && !this.isPassiveSupported()) {
174 e.preventDefault();
175 }
176 }
177 /**
178 * 滚动事件监听
179 */
180 scrollListener = () => {
181 const el = this.scrollComponent;
182
183 const parentNode = this.getParentElement(el);
184
185 this.scrollTop = parentNode.scrollTop;
186 throttle(this.handleScrollY, 500)();
187 }
188
189 /**
190 * @description 根据返回的scrollTop计算当前的索引。
191 */
192 handleScrollY = () => {
193 let rowHeight = this.props.store.getState().rowHeight;
194 //默认显示20条,rowsInView根据定高算的。在非固定高下,这个只是一个大概的值。
195 this.rowsInView = scrollY ? Math.floor(scrollY / rowHeight) : CONFIG.defaultRowsInView;
196
197 let currentIndex = this.currentIndex,
198 startIndex = this.startIndex,
199 endIndex = this.endIndex,
200 treeList = this.treeList,
201 loadCount = this.loadCount,
202 rowsInView = this.rowsInView;
203
204 let index = 0;
205 let tempScrollTop = this.scrollTop;
206 //根据 scrollTop 计算 currentIndex
207 while (tempScrollTop > 0) {
208 tempScrollTop -= rowHeight;
209 if (tempScrollTop > 0) {
210 index += 1;
211 }
212 }
213
214 //true 为向下滚动, false 为向上滚动
215 let isScrollDown = index - currentIndex > 0 ? true : false;
216
217 if (index < 0) index = 0;
218 //如果之前的索引和下一次的不一样则重置索引和滚动的位置
219 this.currentIndex = currentIndex !== index ? index : currentIndex;
220
221 // 如果rowsInView 小于 缓存的数据则重新render
222 // 向下滚动 下临界值超出缓存的endIndex则重新渲染
223 if (isScrollDown && rowsInView + index > endIndex - CONFIG.rowDiff) {
224 startIndex = index - CONFIG.loadBuffer > 0 ? index - CONFIG.loadBuffer : 0;
225 endIndex = startIndex + loadCount;
226 if (endIndex > treeList.length) {
227 endIndex = treeList.length;
228 }
229 if (endIndex > this.endIndex ) {
230 this.startIndex = startIndex;
231 this.endIndex = endIndex;
232 this.sliceTreeList(this.startIndex, this.endIndex);
233 }
234 }
235 // 向上滚动,当前的index是否已经加载(currentIndex),若干上临界值小于startIndex则重新渲染
236 if (!isScrollDown && index < startIndex + CONFIG.rowDiff) {
237 startIndex = index - CONFIG.loadBuffer;
238 if (startIndex < 0) {
239 startIndex = 0;
240 }
241 if (startIndex <= this.startIndex) {
242 this.startIndex = startIndex;
243 this.endIndex = this.startIndex + loadCount;
244 this.sliceTreeList(this.startIndex, this.endIndex);
245 }
246 }
247 }
248
249 /**
250 * 根据 startIndex 和 endIndex 截取数据
251 * @param startIndex
252 * @param endIndex
253 */
254 sliceTreeList = (startIndex, endIndex) => {
255 let newTreeList = []; //存储截取后的新数据
256 // console.log(
257 // "**startIndex**" + startIndex,
258 // "**endIndex**" + endIndex
259 // );
260 newTreeList = this.treeList.slice(startIndex,endIndex);
261 // console.log(JSON.stringify(newTreeList))
262 this.props.handleTreeListChange && this.props.handleTreeListChange(newTreeList, startIndex, endIndex);
263 }
264
265 render() {
266 const {
267 children,
268 element,
269 ref,
270 getScrollParent,
271 treeList,
272 handleTreeListChange,
273 store,
274 ...props
275 } = this.props;
276
277 props.ref = node => {
278 this.scrollComponent = node;
279 if (ref) {
280 ref(node);
281 }
282 };
283
284 const childrenArray = [children];
285
286 return React.createElement(element, props, childrenArray);
287 }
288}