1 |
|
2 |
|
3 |
|
4 | import React, { Component } from 'react';
|
5 | import PropTypes from 'prop-types';
|
6 | import {throttle} from './util';
|
7 | import CONFIG from './config';
|
8 |
|
9 | export 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 |
|
31 | this.rowsInView = CONFIG.defaultRowsInView;
|
32 |
|
33 | this.treeList = props.treeList;
|
34 |
|
35 | this.loadCount = CONFIG.loadBuffer ? this.rowsInView + CONFIG.loadBuffer * 2 : 16;
|
36 |
|
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 |
|
57 |
|
58 |
|
59 |
|
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 |
|
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 |
|
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 |
|
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 |
|
134 |
|
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 |
|
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 |
|
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 |
|
178 |
|
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 |
|
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 |
|
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 |
|
219 | while (tempScrollTop > 0) {
|
220 | tempScrollTop -= rowHeight;
|
221 | if (tempScrollTop > 0) {
|
222 | index += 1;
|
223 | }
|
224 | }
|
225 |
|
226 |
|
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 |
|
234 |
|
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 |
|
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 |
|
263 |
|
264 |
|
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 | }
|