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 | 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 |
|
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 |
|
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 |
|
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 |
|
128 |
|
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 |
|
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 |
|
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 |
|
172 |
|
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 |
|
191 |
|
192 | handleScrollY = () => {
|
193 | let rowHeight = this.props.store.getState().rowHeight;
|
194 |
|
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 |
|
207 | while (tempScrollTop > 0) {
|
208 | tempScrollTop -= rowHeight;
|
209 | if (tempScrollTop > 0) {
|
210 | index += 1;
|
211 | }
|
212 | }
|
213 |
|
214 |
|
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 |
|
222 |
|
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 |
|
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 |
|
251 |
|
252 |
|
253 |
|
254 | sliceTreeList = (startIndex, endIndex) => {
|
255 | let newTreeList = [];
|
256 |
|
257 |
|
258 |
|
259 |
|
260 | newTreeList = this.treeList.slice(startIndex,endIndex);
|
261 |
|
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 | }
|