Add lazyLoadWithDimensions HOC

This commit is contained in:
Vesa Luusua 2018-07-17 21:01:26 +03:00
parent f97cf4827a
commit 71d834bdd5

View file

@ -68,3 +68,109 @@ export const withViewport = Component => {
return WithViewportComponent;
};
/**
* A higher order component (HOC) that lazy loads the current element and provides
* dimensions to the wrapped component as a `dimensions` prop that has
* the shape `{ width: 600, height: 400}`.
*
* @param {React.Component} Component to be wrapped by this HOC
* @param {Object} options pass in options like maxWidth and maxHeight.
*
* @return {Object} HOC component which knows its dimensions
*/
export const lazyLoadWithDimensions = (Component, options) => {
// The resize event is flooded when the browser is resized. We'll
// use a small timeout to throttle changing the viewport since it
// will trigger rerendering.
const THROTTLE_WAIT_MS = 200;
// First render default wait after mounting (small wait for styled paint)
const RENDER_WAIT_MS = 100;
class LazyLoadWithDimensionsComponent extends ReactComponent {
constructor(props) {
super(props);
this.element = null;
this.defaultRenderTimeout = null;
this.state = { width: 0, height: 0 };
this.handleWindowResize = this.handleWindowResize.bind(this);
this.isElementInViewport = this.isElementInViewport.bind(this);
this.setDimensions = throttle(this.setDimensions.bind(this), THROTTLE_WAIT_MS);
}
componentDidMount() {
window.addEventListener('scroll', this.handleWindowResize);
window.addEventListener('resize', this.handleWindowResize);
window.addEventListener('orientationchange', this.handleWindowResize);
this.defaultRenderTimeout = window.setTimeout(() => {
if (this.isElementInViewport()) {
this.handleWindowResize();
}
}, RENDER_WAIT_MS);
}
componentWillUnmount() {
window.removeEventListener('scroll', this.handleWindowResize);
window.removeEventListener('resize', this.handleWindowResize);
window.removeEventListener('orientationchange', this.handleWindowResize);
window.clearTimeout(this.defaultRenderTimeout);
}
handleWindowResize() {
this.setDimensions();
}
setDimensions() {
this.setState(prevState => {
const { clientWidth, clientHeight } = this.element || { clientWidth: 0, clientHeight: 0 };
return { width: clientWidth, height: clientHeight };
});
}
isElementInViewport() {
if (this.element) {
const rect = this.element.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
return false;
}
render() {
const dimensions = this.state;
const { width, height } = dimensions;
const props = { ...this.props, dimensions };
// lazyLoadWithDimensions HOC needs to take all given space
// unless max dimensions are provided through options.
const { maxWidth, maxHeight } = options;
const maxWidthMaybe = maxWidth ? { maxWidth } : {};
const maxHeightMaybe = maxHeight ? { maxHeight } : {};
const style = { width: '100%', height: '100%', ...maxWidthMaybe, ...maxHeightMaybe };
return (
<div
ref={element => {
this.element = element;
}}
style={style}
>
{width !== 0 && height !== 0 ? <Component {...props} /> : null}
</div>
);
}
}
LazyLoadWithDimensionsComponent.displayName = `lazyLoadWithDimensions(${Component.displayName ||
Component.name})`;
return LazyLoadWithDimensionsComponent;
};