I'm trying to implement a lazy-loading image component with React currently.
My original solution was a very simple and basic approach. I used Image
API and listened to onload
event.
There is an example of React hook that was handling this functionality:
function useImage(src) {
const [loaded, setLoaded] = useState(false);
useEffect(() => {
if (!loaded) {
loadImage();
}
function loadImage() {
const img = new Image();
img.onload = () => setLoaded(true);
img.src = src;
}
});
return loaded;
}
and code of the React component looked as follows:
function LazyImage({ src, ...props }) {
const isLoaded = useImage(src);
return (
<div className="container">
{isLoaded ? (
<img src={src} {...props} />
) : (
<div className="preloader" />
)}
</div>
);
}
Now I want to get the benefit of webp
format which is supported by the API where I load the images.
Though browser support of webp
format is not that good, it's possible to provide images inside a <source />
tag and browser will decide which image to load, also <img />
will be used as a fallback.
My React component was updated accordingly:
function LazyImage({ src, ...props }) {
const webpSrc = getWebpSrc(src);
const isLoaded = useImage(src);
return (
<div className="container">
{isLoaded ? (
<picture>
<source srcset={webpSrc} type="image/webp" />
<source srcset={src} type="image/jpeg" />
<img src={src} {...props} />
</picture>
) : (
<div className="preloader" />
)}
</div>
);
}
The problem here is that I'm still listening to the original jpg
src to load in order to display the image. Unfortunately I couldn't just switch to listening to webp
src as in browsers like Safari it will fail to load...
Possibly I can try to load both resources (webp
and jpg
) in parallel and update the state when the first one resolves. However this feels like making more requests instead of optimizing performance actually.
What do you think would be a good approach here? Or am I going in a wrong direction? Thanks!
You can use the IntersectionObserver Api.
First load an placeholder, when the element is intersected you can switch your placeholder with your desired src, this way you can only render the image if the element is in view