2021-04-14 21:02:02 +02:00
|
|
|
// @flow
|
|
|
|
import type { ElementRef } from 'react';
|
2021-05-14 17:00:07 +02:00
|
|
|
import React, { useEffect } from 'react';
|
2021-04-07 07:33:36 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper React hook for lazy loading images
|
|
|
|
* @param elementRef - A React useRef instance to the element to lazy load.
|
2021-08-02 14:33:31 +02:00
|
|
|
* @param backgroundFallback
|
2021-07-08 15:17:37 +02:00
|
|
|
* @param yOffsetPx - Number of pixels from the viewport to start loading.
|
2021-04-07 07:33:36 +02:00
|
|
|
* @param {Array<>} [deps=[]] - The dependencies this lazy-load is reliant on.
|
|
|
|
*/
|
2021-04-14 21:02:02 +02:00
|
|
|
export default function useLazyLoading(
|
|
|
|
elementRef: { current: ?ElementRef<any> },
|
2021-08-02 14:33:31 +02:00
|
|
|
backgroundFallback: string = '',
|
2021-07-08 15:17:37 +02:00
|
|
|
yOffsetPx: number = 500,
|
2021-04-14 21:02:02 +02:00
|
|
|
deps: Array<any> = []
|
|
|
|
) {
|
2021-05-14 17:00:07 +02:00
|
|
|
const [srcLoaded, setSrcLoaded] = React.useState(false);
|
2021-07-08 15:17:37 +02:00
|
|
|
const threshold = 0.01;
|
|
|
|
|
|
|
|
function calcRootMargin(value) {
|
|
|
|
const devicePixelRatio = window.devicePixelRatio || 1.0;
|
|
|
|
if (devicePixelRatio < 1.0) {
|
|
|
|
return Math.ceil(value / devicePixelRatio);
|
|
|
|
}
|
|
|
|
return Math.ceil(value * devicePixelRatio);
|
|
|
|
}
|
2021-05-14 17:00:07 +02:00
|
|
|
|
2022-06-23 14:10:27 +02:00
|
|
|
function loadImgFromDataset(target, backgroundFallback, setSrcLoadedFn) {
|
|
|
|
// lazy-loaded <img>:
|
|
|
|
if (target.dataset.src) {
|
|
|
|
// $FlowFixMe
|
|
|
|
target.src = target.dataset.src;
|
|
|
|
setSrcLoadedFn(true);
|
|
|
|
// No fallback handling here (clients have access to 'onerror' on the image ref).
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// lazy-loaded `background-image`:
|
|
|
|
if (target.dataset.backgroundImage) {
|
|
|
|
if (backgroundFallback) {
|
|
|
|
const tmpImage = new Image();
|
|
|
|
tmpImage.onerror = () => {
|
|
|
|
target.style.backgroundImage = `url(${backgroundFallback})`;
|
|
|
|
};
|
|
|
|
tmpImage.src = target.dataset.backgroundImage;
|
|
|
|
}
|
|
|
|
target.style.backgroundImage = `url(${target.dataset.backgroundImage})`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-07 07:33:36 +02:00
|
|
|
useEffect(() => {
|
|
|
|
if (!elementRef.current) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-06-23 14:10:27 +02:00
|
|
|
if (!window.IntersectionObserver) {
|
|
|
|
loadImgFromDataset(elementRef.current, backgroundFallback, setSrcLoaded);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-04-07 07:33:36 +02:00
|
|
|
const lazyLoadingObserver = new IntersectionObserver(
|
|
|
|
(entries, observer) => {
|
|
|
|
entries.forEach((entry) => {
|
|
|
|
if (entry.intersectionRatio >= threshold) {
|
|
|
|
const { target } = entry;
|
|
|
|
observer.unobserve(target);
|
2022-06-23 14:10:27 +02:00
|
|
|
loadImgFromDataset(target, backgroundFallback, setSrcLoaded);
|
2021-04-07 07:33:36 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
{
|
|
|
|
root: null,
|
2021-07-08 15:17:37 +02:00
|
|
|
rootMargin: `0px 0px ${calcRootMargin(yOffsetPx)}px 0px`,
|
|
|
|
threshold: [threshold],
|
2021-04-07 07:33:36 +02:00
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2021-07-08 15:17:37 +02:00
|
|
|
// $FlowFixMe
|
2021-04-07 07:33:36 +02:00
|
|
|
lazyLoadingObserver.observe(elementRef.current);
|
|
|
|
}, deps);
|
2021-05-14 17:00:07 +02:00
|
|
|
|
|
|
|
return srcLoaded;
|
2021-04-07 07:33:36 +02:00
|
|
|
}
|