UNPKG

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
'use strict'; 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;