59a8b9d663
It was waiting too near the viewport to load on certain zoom levels. This will maintain the same physical distance for the "approaching viewport" buffer regardless of zoom levels.
89 lines
2.6 KiB
JavaScript
89 lines
2.6 KiB
JavaScript
// @flow
|
|
import React from 'react';
|
|
import debounce from 'util/debounce';
|
|
|
|
const DEBOUNCE_SCROLL_HANDLER_MS = 50;
|
|
|
|
function scaleToDevicePixelRatio(value) {
|
|
const devicePixelRatio = window.devicePixelRatio || 1.0;
|
|
if (devicePixelRatio < 1.0) {
|
|
return Math.ceil(value / devicePixelRatio);
|
|
}
|
|
return Math.ceil(value * devicePixelRatio);
|
|
}
|
|
|
|
type Props = {
|
|
children: any,
|
|
skipWait?: boolean,
|
|
placeholder?: any,
|
|
yOffset?: number,
|
|
};
|
|
|
|
export default function WaitUntilOnPage(props: Props) {
|
|
const { yOffset } = props;
|
|
const ref = React.useRef();
|
|
const [shouldRender, setShouldRender] = React.useState(false);
|
|
|
|
const shouldElementRender = React.useCallback(
|
|
(ref) => {
|
|
const element = ref && ref.current;
|
|
if (element) {
|
|
const bounding = element.getBoundingClientRect();
|
|
// $FlowFixMe
|
|
const windowH = window.innerHeight || document.documentElement.clientHeight;
|
|
// $FlowFixMe
|
|
const windowW = window.innerWidth || document.documentElement.clientWidth;
|
|
|
|
const isApproachingViewport = yOffset && bounding.top < windowH + scaleToDevicePixelRatio(yOffset);
|
|
const isInViewport = // also covers "element is larger than viewport".
|
|
bounding.width > 0 &&
|
|
bounding.height > 0 &&
|
|
bounding.bottom >= 0 &&
|
|
bounding.right >= 0 &&
|
|
bounding.top <= windowH &&
|
|
bounding.left <= windowW;
|
|
|
|
return isInViewport || isApproachingViewport;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
[yOffset]
|
|
);
|
|
|
|
// Handles "element is already in viewport when mounted".
|
|
React.useEffect(() => {
|
|
setTimeout(() => {
|
|
if (!shouldRender && shouldElementRender(ref)) {
|
|
setShouldRender(true);
|
|
}
|
|
}, 500);
|
|
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
|
|
// Handles "element scrolled into viewport".
|
|
React.useEffect(() => {
|
|
const handleDisplayingRef = debounce(() => {
|
|
if (shouldElementRender(ref)) {
|
|
setShouldRender(true);
|
|
}
|
|
}, DEBOUNCE_SCROLL_HANDLER_MS);
|
|
|
|
if (ref && ref.current && !shouldRender) {
|
|
window.addEventListener('scroll', handleDisplayingRef);
|
|
window.addEventListener('resize', handleDisplayingRef);
|
|
return () => {
|
|
window.removeEventListener('scroll', handleDisplayingRef);
|
|
window.removeEventListener('resize', handleDisplayingRef);
|
|
};
|
|
}
|
|
}, [ref, setShouldRender, shouldRender, shouldElementRender]);
|
|
|
|
const render = props.skipWait || shouldRender;
|
|
|
|
return (
|
|
<div ref={ref}>
|
|
{render && props.children}
|
|
{!render && props.placeholder !== undefined && props.placeholder}
|
|
</div>
|
|
);
|
|
}
|