UNPKG

8.48 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 // componentDidUpdate() {
57 // const el = this.scrollComponent;
58 // const parentNode = this.getParentElement(el);
59 // parentNode.scrollTop = this.scrollTop;
60 // };
61
62 componentWillUnmount() {
63 this.detachScrollListener();
64 this.detachMousewheelListener();
65 }
66
67 isPassiveSupported() {
68 let passive = false;
69
70 const testOptions = {
71 get passive() {
72 passive = true;
73 }
74 };
75
76 try {
77 document.addEventListener('test', null, testOptions);
78 document.removeEventListener('test', null, testOptions);
79 } catch (e) {
80 // ignore
81 }
82 return passive;
83 }
84
85 eventListenerOptions = () => {
86 let options = this.props.useCapture;
87
88 if (this.isPassiveSupported()) {
89 options = {
90 useCapture: this.props.useCapture,
91 passive: true
92 };
93 }
94 return options;
95 }
96
97 /**
98 * 解除mousewheel事件监听
99 */
100 detachMousewheelListener() {
101 let scrollEl = window;
102 if (this.props.useWindow === false) {
103 scrollEl = this.scrollComponent.parentNode;
104 }
105
106 scrollEl.removeEventListener(
107 'mousewheel',
108 this.mousewheelListener,
109 this.options ? this.options : this.props.useCapture
110 );
111 }
112 /**
113 * 解除scroll事件监听
114 */
115 detachScrollListener() {
116 let scrollEl = window;
117 if (this.props.useWindow === false) {
118 scrollEl = this.getParentElement(this.scrollComponent);
119 }
120
121 scrollEl.removeEventListener(
122 'scroll',
123 this.scrollListener,
124 this.options ? this.options : this.props.useCapture
125 );
126 scrollEl.removeEventListener(
127 'resize',
128 this.scrollListener,
129 this.options ? this.options : this.props.useCapture
130 );
131 }
132 /**
133 * 获取父组件(用户自定义父组件或者当前dom的parentNode)
134 * @param {*} el
135 */
136 getParentElement(el) {
137 const scrollParent =
138 this.props.getScrollParent && this.props.getScrollParent();
139 if (scrollParent != null) {
140 return scrollParent;
141 }
142 return el && el.parentNode;
143 }
144
145 filterProps(props) {
146 return props;
147 }
148 /**
149 * 绑定scroll事件
150 */
151 attachScrollListener() {
152 const { store } = this.props;
153 const parentElement = this.getParentElement(this.scrollComponent);
154 if (!parentElement) {
155 return;
156 }
157 let scrollEl = parentElement;
158 let scrollY = scrollEl && scrollEl.clientHeight;
159
160 let rowHeight = store.getState().rowHeight;
161 //默认显示20条,rowsInView根据定高算的。
162 this.rowsInView = scrollY ? Math.floor(scrollY / rowHeight) : CONFIG.defaultRowsInView;
163
164 scrollEl.addEventListener(
165 'scroll',
166 throttle(this.scrollListener, 150),
167 this.options ? this.options : this.props.useCapture
168 );
169 scrollEl.addEventListener(
170 'resize',
171 throttle(this.scrollListener, 150),
172 this.options ? this.options : this.props.useCapture
173 );
174 }
175
176 mousewheelListener = (e) => {
177 // Prevents Chrome hangups
178 // See: https://stackoverflow.com/questions/47524205/random-high-content-download-time-in-chrome/47684257#47684257
179 if (e.deltaY === 1 && !this.isPassiveSupported()) {
180 e.preventDefault();
181 }
182 }
183 /**
184 * 滚动事件监听
185 */
186 scrollListener = () => {
187 const el = this.scrollComponent;
188 const parentNode = this.getParentElement(el);
189 this.scrollTop = parentNode.scrollTop;
190 this.handleScrollY()
191 }
192
193 /**
194 * @description 根据返回的scrollTop计算当前的索引。
195 */
196 handleScrollY = () => {
197 const { store } = this.props;
198 const parentElement = this.getParentElement(this.scrollComponent);
199 if (!parentElement) {
200 return;
201 }
202 let scrollEl = parentElement;
203 let scrollY = scrollEl && scrollEl.clientHeight;
204
205 let rowHeight = store.getState().rowHeight;
206 //默认显示20条,rowsInView根据定高算的。在非固定高下,这个只是一个大概的值。
207 this.rowsInView = scrollY ? Math.floor(scrollY / rowHeight) : CONFIG.defaultRowsInView;
208
209 let currentIndex = this.currentIndex,
210 startIndex = this.startIndex,
211 endIndex = this.endIndex,
212 treeList = this.treeList,
213 loadCount = this.loadCount,
214 rowsInView = this.rowsInView;
215
216 let index = 0;
217 let tempScrollTop = this.scrollTop;
218 //根据 scrollTop 计算 currentIndex
219 while (tempScrollTop > 0) {
220 tempScrollTop -= rowHeight;
221 if (tempScrollTop > 0) {
222 index += 1;
223 }
224 }
225
226 //true 为向下滚动, false 为向上滚动
227 let isScrollDown = index - currentIndex > 0 ? true : false;
228
229 if (index < 0) index = 0;
230 //如果之前的索引和下一次的不一样则重置索引和滚动的位置
231 this.currentIndex = currentIndex !== index ? index : currentIndex;
232
233 // 如果rowsInView 小于 缓存的数据则重新render
234 // 向下滚动 下临界值超出缓存的endIndex则重新渲染
235 if (isScrollDown && rowsInView + index > endIndex - CONFIG.rowDiff) {
236 startIndex = index - CONFIG.loadBuffer > 0 ? index - CONFIG.loadBuffer : 0;
237 endIndex = startIndex + loadCount;
238 if (endIndex > treeList.length) {
239 endIndex = treeList.length;
240 }
241 if (endIndex > this.endIndex ) {
242 this.startIndex = startIndex;
243 this.endIndex = endIndex;
244 this.sliceTreeList(this.startIndex, this.endIndex);
245 }
246 }
247 // 向上滚动,当前的index是否已经加载(currentIndex),若干上临界值小于startIndex则重新渲染
248 if (!isScrollDown && index < startIndex + CONFIG.rowDiff) {
249 startIndex = index - CONFIG.loadBuffer;
250 if (startIndex < 0) {
251 startIndex = 0;
252 }
253 if (startIndex <= this.startIndex) {
254 this.startIndex = startIndex;
255 this.endIndex = this.startIndex + loadCount;
256 this.sliceTreeList(this.startIndex, this.endIndex);
257 }
258 }
259 }
260
261 /**
262 * 根据 startIndex 和 endIndex 截取数据
263 * @param startIndex
264 * @param endIndex
265 */
266 sliceTreeList = (startIndex, endIndex) => {
267 let newTreeList = []; //存储截取后的新数据
268 newTreeList = this.treeList.slice(startIndex,endIndex);
269 this.props.handleTreeListChange && this.props.handleTreeListChange(newTreeList, startIndex, endIndex);
270 }
271
272 render() {
273 const {
274 children,
275 element,
276 ref,
277 getScrollParent,
278 treeList,
279 handleTreeListChange,
280 store,
281 ...props
282 } = this.props;
283
284 props.ref = node => {
285 this.scrollComponent = node;
286 if (ref) {
287 ref(node);
288 }
289 };
290
291 const childrenArray = [children];
292
293 return React.createElement(element, props, childrenArray);
294 }
295}