react-dynamic-virtual-scroll
Version:
React virtual scroll library
433 lines (365 loc) • 13.5 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react')) :
typeof define === 'function' && define.amd ? define(['exports', 'react'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ReactVirtualScroll = {}, global.React));
}(this, (function (exports, React) { 'use strict';
function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () {
return e[k];
}
});
}
});
}
n['default'] = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespace(React);
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function _extends() {
_extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function");
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
writable: true,
configurable: true
}
});
if (superClass) _setPrototypeOf(subClass, superClass);
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
function _isNativeReflectConstruct() {
if (typeof Reflect === "undefined" || !Reflect.construct) return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === "function") return true;
try {
Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
return true;
} catch (e) {
return false;
}
}
function _objectWithoutPropertiesLoose(source, excluded) {
if (source == null) return {};
var target = {};
var sourceKeys = Object.keys(source);
var key, i;
for (i = 0; i < sourceKeys.length; i++) {
key = sourceKeys[i];
if (excluded.indexOf(key) >= 0) continue;
target[key] = source[key];
}
return target;
}
function _objectWithoutProperties(source, excluded) {
if (source == null) return {};
var target = _objectWithoutPropertiesLoose(source, excluded);
var key, i;
if (Object.getOwnPropertySymbols) {
var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
for (i = 0; i < sourceSymbolKeys.length; i++) {
key = sourceSymbolKeys[i];
if (excluded.indexOf(key) >= 0) continue;
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
target[key] = source[key];
}
}
return target;
}
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return self;
}
function _possibleConstructorReturn(self, call) {
if (call && (typeof call === "object" || typeof call === "function")) {
return call;
}
return _assertThisInitialized(self);
}
function _createSuper(Derived) {
var hasNativeReflectConstruct = _isNativeReflectConstruct();
return function _createSuperInternal() {
var Super = _getPrototypeOf(Derived),
result;
if (hasNativeReflectConstruct) {
var NewTarget = _getPrototypeOf(this).constructor;
result = Reflect.construct(Super, arguments, NewTarget);
} else {
result = Super.apply(this, arguments);
}
return _possibleConstructorReturn(this, result);
};
}
var isInView = function isInView(container, element) {
var containerTop = container.offsetTop;
var elementRect = element.getBoundingClientRect();
var elementTop = elementRect.top;
var elementHeight = elementRect.height;
return elementHeight - (containerTop - elementTop) > 0;
};
var VirtualScroll = /*#__PURE__*/function (_React$Component) {
_inherits(VirtualScroll, _React$Component);
var _super = _createSuper(VirtualScroll);
function VirtualScroll(props) {
var _this;
_classCallCheck(this, VirtualScroll);
_this = _super.call(this, props);
_this.state = {
offset: props.offset
};
_this.lastScrollTop = 0;
_this.avgRowHeight = props.minItemHeight;
return _this;
}
_createClass(VirtualScroll, [{
key: "componentDidMount",
value: function componentDidMount() {
var _this2 = this;
window.requestAnimationFrame(function () {
if (_this2.listRef) {
_this2.listRef.scrollTop = _this2.state.offset * _this2.avgRowHeight;
}
});
}
}, {
key: "componentDidUpdate",
value: function componentDidUpdate(_prevProps, prevState) {
if (prevState.offset > this.state.offset) {
this.updateOffset(prevState);
}
}
}, {
key: "updateOffset",
value: function updateOffset(prevState) {
var offsetDiff = prevState.offset - this.state.offset;
if (this.listRef) {
var el = this.listRef;
var items = el.querySelectorAll(".VS-item");
var heightAdded = 0;
var currOffset = prevState.offset;
var start = Math.min(this.state.offset, this.props.buffer);
var end = start + offsetDiff;
for (var i = Math.min(items.length, end) - 1; i >= start; i--) {
var inView = isInView(el, items[i]);
if (inView) {
currOffset--;
var rowHeight = items[i].clientHeight;
heightAdded += rowHeight;
} else {
break;
}
}
if (items.length < end) {
var diff = end - items.length;
heightAdded += diff * this.props.minItemHeight;
currOffset -= diff;
}
var newAvgRowHeight = currOffset === 0 ? this.props.minItemHeight : (this.avgRowHeight * prevState.offset - heightAdded) / currOffset;
this.setState({
offset: currOffset
});
this.avgRowHeight = Math.max(this.props.minItemHeight, newAvgRowHeight);
}
}
}, {
key: "onScrollHandler",
value: function onScrollHandler(event) {
if (this.listRef) {
var _this$props = this.props,
totalLength = _this$props.totalLength,
length = _this$props.length,
buffer = _this$props.buffer;
var offset = this.state.offset;
var avgRowHeight = this.avgRowHeight;
var el = this.listRef;
var scrollTop = el.scrollTop;
var direction = Math.floor(scrollTop - this.lastScrollTop);
if (direction === 0) return;
var items = el.querySelectorAll(".VS-item");
var newOffset = offset;
var newAvgRowHeight = avgRowHeight;
var start = Math.min(offset, buffer);
if (direction > 0) {
if (offset < totalLength - length) {
var heightAdded = 0;
for (var i = start; i < items.length; i++) {
var inView = isInView(el, items[i]);
var rowHeight = items[i].clientHeight;
if (!inView) {
heightAdded += rowHeight;
newOffset++;
} else {
break;
}
}
if (heightAdded < direction) {
var heightLeft = direction - heightAdded;
var offsetToBeAdded = Math.floor(heightLeft / this.props.minItemHeight);
newOffset += offsetToBeAdded;
heightAdded += offsetToBeAdded * this.props.minItemHeight;
}
newAvgRowHeight = newOffset > 0 ? (offset * avgRowHeight + heightAdded) / newOffset : this.props.minItemHeight;
this.setState({
offset: Math.min(newOffset, totalLength - length)
});
this.avgRowHeight = Math.max(this.props.minItemHeight, newAvgRowHeight);
}
} else {
var scrollDiff = items[start].getBoundingClientRect().y - el.getBoundingClientRect().y;
if (scrollDiff > 0) {
var offsetDiff = Math.floor(scrollDiff / this.props.minItemHeight) || 1;
var _newOffset = offset - offsetDiff;
if (_newOffset < totalLength - (length + buffer)) {
this.setState({
offset: Math.max(0, _newOffset)
});
}
}
}
this.lastScrollTop = scrollTop;
}
if (this.props.onScroll) this.props.onScroll(event);
}
}, {
key: "renderItems",
value: function renderItems(start, end) {
var renderItem = this.props.renderItem;
return Array.from({
length: end - start + 1
}, function (_, index) {
var rowIndex = start + index;
var component = renderItem(rowIndex);
return /*#__PURE__*/React__namespace.cloneElement(component, {
key: rowIndex,
className: ["VS-item", component.props.className].join(' ').trim()
});
});
}
}, {
key: "render",
value: function render() {
var _this3 = this;
var _this$props2 = this.props,
totalLength = _this$props2.totalLength,
length = _this$props2.length,
buffer = _this$props2.buffer;
_this$props2.minItemHeight;
var forwardRef = _this$props2.forwardRef;
_this$props2.offset;
_this$props2.renderItem;
var rest = _objectWithoutProperties(_this$props2, ["totalLength", "length", "buffer", "minItemHeight", "forwardRef", "offset", "renderItem"]);
var _this$state = this.state,
init = _this$state.init,
offset = _this$state.offset;
var avgRowHeight = this.avgRowHeight;
var start = Math.max(0, offset - buffer);
var end = Math.min(offset + (length + buffer) - 1, totalLength - 1);
var topPadding = Math.max(0, start * avgRowHeight);
var bottomPadding = Math.max(0, (totalLength - end - 1) * avgRowHeight);
return /*#__PURE__*/React__namespace.createElement("div", _extends({}, rest, {
ref: function ref(el) {
_this3.listRef = el;
if (forwardRef) forwardRef.current = el;
if (!init) _this3.setState({
init: true
});
},
onScroll: this.onScrollHandler.bind(this)
}), init && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("div", {
style: {
flexShrink: 0,
height: topPadding
}
}), this.renderItems(start, end), /*#__PURE__*/React__namespace.createElement("div", {
style: {
flexShrink: 0,
height: bottomPadding
}
})));
}
}]);
return VirtualScroll;
}(React__namespace.Component);
_defineProperty(VirtualScroll, "defaultProps", {
buffer: 10,
length: 30,
offset: 0
});
var index = /*#__PURE__*/React__namespace.forwardRef(function (props, ref) {
return /*#__PURE__*/React__namespace.createElement(VirtualScroll, _extends({
key: props.totalLength,
forwardRef: ref
}, props));
});
exports.default = index;
Object.defineProperty(exports, '__esModule', { value: true });
})));