UNPKG

@dcasia/react-lazy-load-image-component-improved

Version:

React Component to lazy load images using a HOC to track window scroll position.

165 lines (137 loc) 3.98 kB
import React from 'react'; import ReactDOM from 'react-dom'; import { PropTypes } from 'prop-types'; import isIntersectionObserverAvailable from '../utils/intersection-observer'; const checkIntersections = entries => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.onVisible(); } }); }; const LAZY_LOAD_OBSERVERS = {}; const getObserver = threshold => { LAZY_LOAD_OBSERVERS[threshold] = LAZY_LOAD_OBSERVERS[threshold] || new IntersectionObserver(checkIntersections, { rootMargin: threshold + 'px', }); return LAZY_LOAD_OBSERVERS[threshold]; }; class PlaceholderWithoutTracking extends React.Component { constructor(props) { super(props); this.supportsObserver = !props.scrollPosition && props.useIntersectionObserver && isIntersectionObserverAvailable(); if (this.supportsObserver) { const { threshold } = props; this.observer = getObserver(threshold); } } componentDidMount() { if (this.placeholder && this.observer) { this.placeholder.onVisible = this.props.onVisible; this.observer.observe(this.placeholder); } if (!this.supportsObserver) { this.updateVisibility(); } } componentWillUnmount() { if (this.observer) { this.observer.unobserve(this.placeholder); } } componentDidUpdate() { if (!this.supportsObserver) { this.updateVisibility(); } } getPlaceholderBoundingBox(scrollPosition = this.props.scrollPosition) { const boundingRect = this.placeholder.getBoundingClientRect(); const style = ReactDOM.findDOMNode(this.placeholder).style; const margin = { left: parseInt(style.getPropertyValue('margin-left'), 10) || 0, top: parseInt(style.getPropertyValue('margin-top'), 10) || 0, }; return { bottom: scrollPosition.y + boundingRect.bottom + margin.top, left: scrollPosition.x + boundingRect.left + margin.left, right: scrollPosition.x + boundingRect.right + margin.left, top: scrollPosition.y + boundingRect.top + margin.top, }; } isPlaceholderInViewport() { if (typeof window === 'undefined' || !this.placeholder) { return false; } const { scrollPosition, threshold } = this.props; const boundingBox = this.getPlaceholderBoundingBox(scrollPosition); const viewport = { bottom: scrollPosition.y + window.innerHeight, left: scrollPosition.x, right: scrollPosition.x + window.innerWidth, top: scrollPosition.y, }; return Boolean( viewport.top - threshold <= boundingBox.bottom && viewport.bottom + threshold >= boundingBox.top && viewport.left - threshold <= boundingBox.right && viewport.right + threshold >= boundingBox.left ); } updateVisibility() { if (this.isPlaceholderInViewport()) { this.props.onVisible(); } } render() { const { className, height, placeholder, style, width } = this.props; if (placeholder && typeof placeholder.type !== 'function') { return React.cloneElement(placeholder, { ref: el => (this.placeholder = el), }); } const styleProp = { display: 'inline-block', ...style, }; if (typeof width !== 'undefined') { styleProp.width = width; } if (typeof height !== 'undefined') { styleProp.height = height; } return ( <span className={className} ref={el => (this.placeholder = el)} style={styleProp} > {placeholder} </span> ); } } PlaceholderWithoutTracking.propTypes = { onVisible: PropTypes.func.isRequired, className: PropTypes.string, height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), placeholder: PropTypes.element, threshold: PropTypes.number, useIntersectionObserver: PropTypes.bool, scrollPosition: PropTypes.shape({ x: PropTypes.number.isRequired, y: PropTypes.number.isRequired, }), width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), }; PlaceholderWithoutTracking.defaultProps = { className: '', placeholder: null, threshold: 100, useIntersectionObserver: true, }; export default PlaceholderWithoutTracking;