UNPKG

16.1 kBJavaScriptView Raw
1'use strict';
2
3exports.__esModule = true;
4
5var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
6
7var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
8
9var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
10
11var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
12
13var _inherits2 = require('babel-runtime/helpers/inherits');
14
15var _inherits3 = _interopRequireDefault(_inherits2);
16
17var _class, _temp;
18
19var _propTypes = require('prop-types');
20
21var _propTypes2 = _interopRequireDefault(_propTypes);
22
23var _react = require('react');
24
25var _react2 = _interopRequireDefault(_react);
26
27var _classnames = require('classnames');
28
29var _classnames2 = _interopRequireDefault(_classnames);
30
31var _reactLifecyclesCompat = require('react-lifecycles-compat');
32
33var _reactDom = require('react-dom');
34
35var _util = require('../util');
36
37function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
38
39var NOOP = function NOOP() {};
40var MAX_SYNC_UPDATES = 40;
41
42var isEqualSubset = function isEqualSubset(a, b) {
43 for (var key in b) {
44 if (a[key] !== b[key]) {
45 return false;
46 }
47 }
48
49 return true;
50};
51
52var getOffset = function getOffset(el) {
53 var offset = el.clientLeft || 0;
54 do {
55 offset += el.offsetTop || 0;
56 el = el.offsetParent;
57 } while (el);
58 return offset;
59};
60
61var constrain = function constrain(from, size, _ref) {
62 var children = _ref.children,
63 minSize = _ref.minSize;
64
65 var length = children && children.length;
66 size = Math.max(size, minSize);
67 if (size > length) {
68 size = length;
69 }
70 from = from ? Math.max(Math.min(from, length - size), 0) : 0;
71
72 return { from: from, size: size };
73};
74/** VirtualList */
75var VirtualList = (_temp = _class = function (_Component) {
76 (0, _inherits3.default)(VirtualList, _Component);
77
78 function VirtualList(props) {
79 (0, _classCallCheck3.default)(this, VirtualList);
80
81 var _this = (0, _possibleConstructorReturn3.default)(this, _Component.call(this, props));
82
83 var jumpIndex = props.jumpIndex;
84
85 var _constrain = constrain(jumpIndex, 0, props),
86 from = _constrain.from,
87 size = _constrain.size;
88
89 _this.state = { from: from, size: size };
90 _this.cache = {};
91 _this.cacheAdd = {};
92 _this.scrollTo = _this.scrollTo.bind(_this);
93 _this.cachedScroll = null;
94 _this.unstable = false;
95 _this.updateCounter = 0;
96 return _this;
97 }
98
99 VirtualList.getDerivedStateFromProps = function getDerivedStateFromProps(nextProps, prevState) {
100 var from = prevState.from,
101 size = prevState.size;
102
103
104 return constrain(from, size, nextProps);
105 };
106
107 VirtualList.prototype.componentDidMount = function componentDidMount() {
108 var jumpIndex = this.props.jumpIndex;
109
110
111 this.updateFrameAndClearCache = this.updateFrameAndClearCache.bind(this);
112
113 _util.events.on(window, 'resize', this.updateFrameAndClearCache);
114
115 this.updateFrame(this.scrollTo.bind(this, jumpIndex));
116 };
117
118 VirtualList.prototype.componentDidUpdate = function componentDidUpdate(prevProps) {
119 var _this2 = this;
120
121 var oldIndex = prevProps.jumpIndex;
122 var newIndex = this.props.jumpIndex;
123
124 if (oldIndex !== newIndex) {
125 this.updateFrame(this.scrollTo.bind(this, newIndex));
126 }
127
128 // If the list has reached an unstable state, prevent an infinite loop.
129 if (this.unstable) {
130 return;
131 }
132
133 if (++this.updateCounter > MAX_SYNC_UPDATES) {
134 this.unstable = true;
135 }
136
137 if (!this.updateCounterTimeoutId) {
138 this.updateCounterTimeoutId = setTimeout(function () {
139 _this2.updateCounter = 0;
140 delete _this2.updateCounterTimeoutId;
141 }, 0);
142 }
143
144 this.updateFrame();
145 };
146
147 VirtualList.prototype.componentWillUnmount = function componentWillUnmount() {
148 _util.events.off(window, 'resize', this.updateFrameAndClearCache);
149
150 _util.events.off(this.scrollParent, 'scroll', this.updateFrameAndClearCache);
151 _util.events.off(this.scrollParent, 'mousewheel', NOOP);
152 };
153
154 VirtualList.prototype.maybeSetState = function maybeSetState(b, cb) {
155 if (isEqualSubset(this.state, b)) {
156 return cb();
157 }
158
159 this.setState(b, cb);
160 };
161
162 VirtualList.prototype.getEl = function getEl() {
163 return this.el || this.items || {};
164 };
165
166 VirtualList.prototype.getScrollParent = function getScrollParent() {
167 var el = this.getEl();
168 el = el.parentElement;
169
170 switch (window.getComputedStyle(el).overflowY) {
171 case 'auto':
172 case 'scroll':
173 case 'overlay':
174 case 'visible':
175 return el;
176 }
177
178 return window;
179 };
180
181 VirtualList.prototype.getScroll = function getScroll() {
182 // Cache scroll position as this causes a forced synchronous layout.
183 // if (typeof this.cachedScroll === 'number') {
184 // return this.cachedScroll;
185 // }
186 var scrollParent = this.scrollParent;
187
188 var scrollKey = 'scrollTop';
189 var actual = scrollParent === window ? // Firefox always returns document.body[scrollKey] as 0 and Chrome/Safari
190 // always return document.documentElement[scrollKey] as 0, so take
191 // whichever has a value.
192 document.body[scrollKey] || document.documentElement[scrollKey] : scrollParent[scrollKey];
193 var max = this.getScrollSize() - this.getViewportSize();
194
195 var scroll = Math.max(0, Math.min(actual, max));
196 var el = this.getEl();
197 this.cachedScroll = getOffset(scrollParent) + scroll - getOffset(el);
198
199 return this.cachedScroll;
200 };
201
202 VirtualList.prototype.setScroll = function setScroll(offset) {
203 var scrollParent = this.scrollParent;
204
205 offset += getOffset(this.getEl());
206 if (scrollParent === window) {
207 return window.scrollTo(0, offset);
208 }
209
210 offset -= getOffset(this.scrollParent);
211 scrollParent.scrollTop = offset;
212 };
213
214 VirtualList.prototype.getViewportSize = function getViewportSize() {
215 var scrollParent = this.scrollParent;
216
217 return scrollParent === window ? window.innerHeight : scrollParent.clientHeight;
218 };
219
220 VirtualList.prototype.getScrollSize = function getScrollSize() {
221 var scrollParent = this.scrollParent;
222 var _document = document,
223 body = _document.body,
224 documentElement = _document.documentElement;
225
226 var key = 'scrollHeight';
227 return scrollParent === window ? Math.max(body[key], documentElement[key]) : scrollParent[key];
228 };
229
230 VirtualList.prototype.getStartAndEnd = function getStartAndEnd() {
231 var threshold = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.props.threshold;
232
233 var scroll = this.getScroll();
234
235 var trueScroll = scroll;
236 var start = Math.max(0, trueScroll - threshold);
237 var end = trueScroll + this.getViewportSize() + threshold;
238
239 return { start: start, end: end };
240 };
241
242 // Called by 'scroll' and 'resize' events, clears scroll position cache.
243
244
245 VirtualList.prototype.updateFrameAndClearCache = function updateFrameAndClearCache(cb) {
246 this.cachedScroll = null;
247 return this.updateFrame(cb);
248 };
249
250 VirtualList.prototype.updateFrame = function updateFrame(cb) {
251 this.updateScrollParent();
252
253 if (typeof cb !== 'function') {
254 cb = NOOP;
255 }
256
257 return this.updateVariableFrame(cb);
258 };
259
260 VirtualList.prototype.updateScrollParent = function updateScrollParent() {
261 var prev = this.scrollParent;
262 this.scrollParent = this.getScrollParent();
263
264 if (prev === this.scrollParent) {
265 return;
266 }
267 if (prev) {
268 _util.events.off(prev, 'scroll', this.updateFrameAndClearCache);
269 _util.events.off(prev, 'mousewheel', NOOP);
270 }
271
272 _util.events.on(this.scrollParent, 'scroll', this.updateFrameAndClearCache);
273 _util.events.on(this.scrollParent, 'mousewheel', NOOP);
274
275 // You have to attach mousewheel listener to the scrollable element.
276 // Just an empty listener. After that onscroll events will be fired synchronously.
277 };
278
279 VirtualList.prototype.updateVariableFrame = function updateVariableFrame(cb) {
280 if (!this.props.itemSizeGetter) {
281 this.cacheSizes();
282 }
283
284 var _getStartAndEnd = this.getStartAndEnd(),
285 start = _getStartAndEnd.start,
286 end = _getStartAndEnd.end;
287
288 var _props = this.props,
289 pageSize = _props.pageSize,
290 children = _props.children;
291
292 var length = children.length;
293 var space = 0;
294 var from = 0;
295 var size = 0;
296 var maxFrom = length - 1;
297
298 while (from < maxFrom) {
299 var itemSize = this.getSizeOf(from);
300 if (itemSize === null || itemSize === undefined || space + itemSize > start) {
301 break;
302 }
303 space += itemSize;
304 ++from;
305 }
306
307 var maxSize = length - from;
308
309 while (size < maxSize && space < end) {
310 var _itemSize = this.getSizeOf(from + size);
311 if (_itemSize === null || _itemSize === undefined) {
312 size = Math.min(size + pageSize, maxSize);
313 break;
314 }
315 space += _itemSize;
316 ++size;
317 }
318
319 this.maybeSetState({ from: from, size: size }, cb);
320 };
321
322 VirtualList.prototype.getSpaceBefore = function getSpaceBefore(index) {
323 var cache = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
324
325 if (!index) {
326 return 0;
327 }
328 if (cache[index] !== null && cache[index] !== undefined) {
329 return cache[index] || 0;
330 }
331
332 // Find the closest space to index there is a cached value for.
333 var from = index;
334 while (from > 0 && (cache[from] === null || cache[from] === undefined)) {
335 from--;
336 }
337
338 // Finally, accumulate sizes of items from - index.
339 var space = cache[from] || 0;
340 for (var i = from; i < index; ++i) {
341 cache[i] = space;
342 var itemSize = this.getSizeOf(i);
343 if (itemSize === null || itemSize === undefined) {
344 break;
345 }
346 space += itemSize;
347 }
348
349 cache[index] = space;
350
351 return cache[index] || 0;
352 };
353
354 VirtualList.prototype.cacheSizes = function cacheSizes() {
355 var cache = this.cache;
356 var from = this.state.from;
357 var _items = this.items,
358 children = _items.children,
359 _items$props = _items.props,
360 props = _items$props === undefined ? {} : _items$props;
361
362 var itemEls = children || props.children || [];
363
364 try {
365 // <Select useVirtual /> 模式下,在快速点击切换Tab的情况下(Select实例快速出现、消失) 有时会出现this.items不存在,导致页面报错。怀疑是Select的异步timer渲染逻辑引起的
366 for (var i = 0, l = itemEls.length; i < l; ++i) {
367 var ulRef = (0, _reactDom.findDOMNode)(this.items);
368 var height = ulRef.children[i].offsetHeight;
369 if (height > 0) {
370 cache[from + i] = height;
371 }
372 }
373 } catch (error) {
374 // ...
375 }
376 };
377
378 VirtualList.prototype.getSizeOf = function getSizeOf(index) {
379 var cache = this.cache;
380 var _props2 = this.props,
381 itemSizeGetter = _props2.itemSizeGetter,
382 jumpIndex = _props2.jumpIndex;
383
384 // Try the cache.
385
386 if (index in cache) {
387 return cache[index];
388 }
389 if (itemSizeGetter) {
390 return itemSizeGetter(index);
391 }
392
393 if (!this.defaultItemHeight && jumpIndex > -1) {
394 var keysList = Object.keys(this.cache);
395 var len = keysList.length;
396 var height = this.cache[len - 1];
397 this.defaultItemHeight = height;
398 }
399
400 if (this.defaultItemHeight) {
401 return this.defaultItemHeight;
402 }
403 };
404
405 VirtualList.prototype.scrollTo = function scrollTo(index) {
406 this.setScroll(this.getSpaceBefore(index, this.cacheAdd));
407 };
408
409 VirtualList.prototype.renderMenuItems = function renderMenuItems() {
410 var _this3 = this;
411
412 var _props3 = this.props,
413 children = _props3.children,
414 itemsRenderer = _props3.itemsRenderer;
415 var _state = this.state,
416 from = _state.from,
417 size = _state.size;
418
419 var items = [];
420
421 for (var i = 0; i < size; ++i) {
422 items.push(children[from + i]);
423 }
424
425 return itemsRenderer(items, function (c) {
426 _this3.items = c;
427 return _this3.items;
428 });
429 };
430
431 VirtualList.prototype.render = function render() {
432 var _cx,
433 _this4 = this;
434
435 var _props4 = this.props,
436 _props4$children = _props4.children,
437 children = _props4$children === undefined ? [] : _props4$children,
438 prefix = _props4.prefix,
439 className = _props4.className;
440
441 var length = children.length;
442 var from = this.state.from;
443
444 var items = this.renderMenuItems();
445
446 var style = { position: 'relative' };
447
448 var size = this.getSpaceBefore(length, {});
449
450 if (size) {
451 style.height = size;
452 }
453 var offset = this.getSpaceBefore(from, this.cacheAdd);
454 var transform = 'translate(0px, ' + offset + 'px)';
455 var listStyle = {
456 msTransform: transform,
457 WebkitTransform: transform,
458 transform: transform
459 };
460
461 var cls = (0, _classnames2.default)((_cx = {}, _cx[prefix + 'virtual-list-wrapper'] = true, _cx[className] = !!className, _cx));
462
463 return _react2.default.createElement(
464 'div',
465 {
466 className: cls,
467 style: style,
468 ref: function ref(c) {
469 _this4.el = c;
470 return _this4.el;
471 }
472 },
473 _react2.default.createElement(
474 'div',
475 { style: listStyle },
476 items
477 )
478 );
479 };
480
481 return VirtualList;
482}(_react.Component), _class.displayName = 'VirtualList', _class.propTypes = {
483 prefix: _propTypes2.default.string,
484 /**
485 * 渲染的子节点
486 */
487 children: _propTypes2.default.any,
488 /**
489 * 最小加载数量
490 */
491 minSize: _propTypes2.default.number,
492 /**
493 * 一屏数量
494 */
495 pageSize: _propTypes2.default.number,
496 /**
497 * 父渲染函数,默认为 (items, ref) => <ul ref={ref}>{items}</ul>
498 */
499 itemsRenderer: _propTypes2.default.func,
500 /**
501 * 缓冲区高度
502 */
503 threshold: _propTypes2.default.number,
504 /**
505 * 获取item高度的函数
506 */
507 itemSizeGetter: _propTypes2.default.func,
508 /**
509 * 设置跳转位置,需要设置 itemSizeGetter 才能生效, 不设置认为元素等高并取第一个元素高度作为默认高
510 */
511 jumpIndex: _propTypes2.default.number,
512 className: _propTypes2.default.string
513}, _class.defaultProps = {
514 prefix: 'next-',
515 itemsRenderer: function itemsRenderer(items, ref) {
516 return _react2.default.createElement(
517 'ul',
518 { ref: ref },
519 items
520 );
521 },
522 minSize: 1,
523 pageSize: 10,
524 jumpIndex: 0,
525 threshold: 100
526}, _temp);
527VirtualList.displayName = 'VirtualList';
528exports.default = (0, _reactLifecyclesCompat.polyfill)(VirtualList);
529module.exports = exports['default'];
\No newline at end of file