react-component-lazy-loader
Version:
A package which lazily loads it's children components based on viewport visibility of the component. Useful in lazyloading images or any other custom component.
267 lines (220 loc) • 11.6 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var _createClass = 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); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _react = require('react');
var React = _interopRequireWildcard(_react);
var _reactDom = require('react-dom');
var _propTypes = require('prop-types');
var _lodash = require('lodash.throttle');
var _lodash2 = _interopRequireDefault(_lodash);
var _lodash3 = require('lodash.get');
var _lodash4 = _interopRequireDefault(_lodash3);
var _utils = require('./utils');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var ReactComponentLazyLoader = function (_React$Component) {
_inherits(ReactComponentLazyLoader, _React$Component);
function ReactComponentLazyLoader(props) {
_classCallCheck(this, ReactComponentLazyLoader);
var _this = _possibleConstructorReturn(this, (ReactComponentLazyLoader.__proto__ || Object.getPrototypeOf(ReactComponentLazyLoader)).call(this, props));
_this.setPlaceholderNodePosition = function () {
var elementPos = _this.placeholderNode.getBoundingClientRect();
_this.setState({
distanceOfElementFromTop: elementPos.top,
distanceOfElementFromLeft: elementPos.left
});
};
_this.handleViewportChangeEvents = function () {
var _this$props = _this.props,
noLazyHorizontalScroll = _this$props.noLazyHorizontalScroll,
wrapperID = _this$props.wrapperID;
if (noLazyHorizontalScroll && _this.loadOnVerticalScroll()) {
_this.setState({ renderLazyLoadedComponent: true });
_this.removeEventListeners();
} else if (wrapperID) {
var wrapperNode = document.getElementById('' + wrapperID);
if (_this.loadOnVerticalScroll() && _this.loadOnHorizontalScroll()) {
_this.setState({ renderLazyLoadedComponent: true });
_this.removeEventListeners(wrapperNode);
}
if (!_this.horizontalEventAdded) {
wrapperNode.addEventListener('scroll', _this.handleWrapperScroll);
wrapperNode.addEventListener('resize', _this.handleWrapperScroll);
_this.horizontalEventAdded = true;
}
} else if (_this.loadOnVerticalScroll() && _this.loadOnHorizontalScroll()) {
_this.setState({ renderLazyLoadedComponent: true });
_this.removeEventListeners();
}
};
_this.handleWrapperScroll = function () {
var wrapperID = _this.props.wrapperID;
var distanceOfElementFromLeft = _this.state.distanceOfElementFromLeft;
var wrapperNode = document.getElementById('' + wrapperID);
var scrollLeft = wrapperNode.scrollLeft;
var loadOnHorizontalScroll = distanceOfElementFromLeft < window.innerWidth + scrollLeft;
if (_this.loadOnVerticalScroll() && loadOnHorizontalScroll) {
_this.setState({ renderLazyLoadedComponent: true });
_this.removeEventListeners(wrapperNode);
}
};
_this.addEventListeners = function () {
window.addEventListener('resize', _this.handleViewportChangeEvents);
window.addEventListener('scroll', _this.handleViewportChangeEvents);
};
_this.removeEventListeners = function (wrapperNode) {
window.removeEventListener('resize', _this.handleViewportChangeEvents);
window.removeEventListener('scroll', _this.handleViewportChangeEvents);
if (wrapperNode) {
wrapperNode.removeEventListener('scroll', _this.handleWrapperScroll);
wrapperNode.removeEventListener('resize', _this.handleWrapperScroll);
}
};
_this.loadOnVerticalScroll = function () {
var distanceOfElementFromTop = _this.state.distanceOfElementFromTop;
var thresholdY = _this.props.thresholdY;
var scrollYDistance = (0, _utils.currentScrollPosition)().scrollY;
var scrolledFromTop = window.innerHeight + scrollYDistance;
return distanceOfElementFromTop - thresholdY < scrolledFromTop;
};
_this.loadOnHorizontalScroll = function () {
var distanceOfElementFromLeft = _this.state.distanceOfElementFromLeft;
var thresholdX = _this.props.thresholdX;
var scrollXDistance = (0, _utils.currentScrollPosition)().scrollX;
var scrolledFromLeft = window.innerWidth + scrollXDistance;
return distanceOfElementFromLeft - thresholdX < scrolledFromLeft || distanceOfElementFromLeft < window.innerWidth;
};
_this.createObserver = function () {
var _this$props2 = _this.props,
thresholdX = _this$props2.thresholdX,
thresholdY = _this$props2.thresholdY;
var options = {
root: null,
rootMargin: '0px ' + thresholdX + 'px ' + thresholdY + 'px 0px',
threshold: 0.0
};
_this.observer = new IntersectionObserver(_this.handleViewportChange, options);
_this.observer.observe(_this.placeholderNode);
};
_this.handleViewportChange = function (changes) {
var noLazyHorizontalScroll = _this.props.noLazyHorizontalScroll;
changes.forEach(function (change) {
if (noLazyHorizontalScroll) {
if (change.intersectionRatio > 0) {
_this.setState({ renderLazyLoadedComponent: true });
_this.observer.disconnect();
}
} else {
var scrollXDistance = (0, _utils.currentScrollPosition)().scrollX;
var scrollYDistance = (0, _utils.currentScrollPosition)().scrollY;
var scrolledFromTop = (0, _lodash4.default)(change, 'rootBounds.height', 0) + scrollYDistance;
var scrolledFromLeft = (0, _lodash4.default)(change, 'rootBounds.width', 0) + scrollXDistance;
var distanceOfElementFromTop = (0, _lodash4.default)(change, 'boundingClientRect.top', 0);
var distanceOfElementFromLeft = (0, _lodash4.default)(change, 'boundingClientRect.left', 0);
if (distanceOfElementFromTop < scrolledFromTop && distanceOfElementFromLeft < scrolledFromLeft) {
_this.setState({ renderLazyLoadedComponent: true });
_this.observer.disconnect();
}
}
});
};
_this.callCallback = function () {
var callback = _this.props.callback;
var callbackCalled = _this.state.callbackCalled;
if (!callbackCalled && typeof callback === 'function') {
callback();
_this.setState({ callbackCalled: true });
}
};
_this.state = {
renderLazyLoadedComponent: false,
isIntersectionObserverAvailableinWindow: (0, _utils.isIntersectionObserverSupported)(),
callbackCalled: false,
distanceOfElementFromTop: undefined,
distanceOfElementFromLeft: undefined
};
_this.observer = null;
_this.placeholderNode = null;
var throttleWait = _this.props.throttleWait;
_this.handleViewportChange = _this.handleViewportChange.bind(_this);
_this.handleWrapperScroll = _this.handleWrapperScroll.bind(_this);
_this.handleWrapperScroll = (0, _lodash2.default)(_this.handleWrapperScroll, throttleWait);
_this.addEventListeners = _this.addEventListeners.bind(_this);
_this.removeEventListeners = _this.removeEventListeners.bind(_this);
_this.handleViewportChangeEvents = _this.handleViewportChangeEvents.bind(_this);
_this.handleViewportChangeEvents = (0, _lodash2.default)(_this.handleViewportChangeEvents, throttleWait);
_this.horizontalEventAdded = false;
_this.forcefulHorizontalScroll = false;
return _this;
}
_createClass(ReactComponentLazyLoader, [{
key: 'componentDidMount',
value: function componentDidMount() {
var isIntersectionObserverAvailableinWindow = this.state.isIntersectionObserverAvailableinWindow;
this.placeholderNode = (0, _reactDom.findDOMNode)(this);
if (_typeof(this.placeholderNode) === 'object') {
this.setPlaceholderNodePosition();
if (isIntersectionObserverAvailableinWindow) {
this.createObserver();
} else {
this.addEventListeners();
}
}
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
var isIntersectionObserverAvailableinWindow = this.state.isIntersectionObserverAvailableinWindow;
if (isIntersectionObserverAvailableinWindow) {
this.observer.disconnect();
} else {
window.removeEventListener('resize', this.handleViewportChangeEvents);
window.removeEventListener('scroll', this.handleViewportChangeEvents);
}
}
}, {
key: 'render',
value: function render() {
var _props = this.props,
children = _props.children,
placeholder = _props.placeholder;
var renderLazyLoadedComponent = this.state.renderLazyLoadedComponent;
if (renderLazyLoadedComponent) {
return React.createElement(
React.Fragment,
null,
children,
this.callCallback()
);
}
return placeholder;
}
}]);
return ReactComponentLazyLoader;
}(React.Component);
ReactComponentLazyLoader.defaultProps = {
placeholder: React.createElement('div', null),
thresholdX: 0,
thresholdY: 0,
wrapperID: null,
callback: null,
noLazyHorizontalScroll: false,
throttleWait: 75
};
ReactComponentLazyLoader.propTypes = {
callback: _propTypes.PropTypes.func,
children: _propTypes.PropTypes.node.isRequired,
noLazyHorizontalScroll: _propTypes.PropTypes.bool,
placeholder: _propTypes.PropTypes.node,
thresholdX: _propTypes.PropTypes.number,
thresholdY: _propTypes.PropTypes.number,
throttleWait: _propTypes.PropTypes.number,
wrapperID: _propTypes.PropTypes.string
};
exports.default = ReactComponentLazyLoader;
;