My website is too heavy because it downloads 200-400 images after fetching data from the server (Google’s Firebase Firestore).
I came up with two solutions and I hope somebody answers one of them:
- I want to set each img to have a loading state and enable visitors to see the placeholder image until it is loaded. As I don’t know how many images I get until fetching data from the server, I find it hard to initialize image loading statuses by useState. Is this possible? Then, how?
- How can I Lazy load images? Images are initialized with a placeholder. When a scroll comes near an image, the image starts to download replacing the placeholder.
function sample() {}{
const [items, setItems] = useState([])
const [imgLoading, setImgLoading] = useState(true) // imgLoading might have to be boolean[]
useEffect(() => {
axios.get(url).
.then(response => setItems(response.data))
}, [])
return (
items.map(item => <img src={item.imageUrl} onLoad={setImgLoading(false)} />)
)
}
Advertisement
Answer
There are libraries for this, but if you want to roll your own, you can use an IntersectionObserver, something like this:
const { useState, useRef, useEffect } = React;
const LazyImage = (imageProps) => {
const [shouldLoad, setShouldLoad] = useState(false);
const placeholderRef = useRef(null);
useEffect(() => {
if (!shouldLoad && placeholderRef.current) {
const observer = new IntersectionObserver(([{ intersectionRatio }]) => {
if (intersectionRatio > 0) {
setShouldLoad(true);
}
});
observer.observe(placeholderRef.current);
return () => observer.disconnect();
}
}, [shouldLoad, placeholderRef]);
return (shouldLoad
? <img {...imageProps}/>
: <div className="img-placeholder" ref={placeholderRef}/>
);
};
ReactDOM.render(
<div className="scroll-list">
<LazyImage src='https://i.insider.com/536a52d9ecad042e1fb1a778?width=1100&format=jpeg&auto=webp'/>
<LazyImage src='https://www.denofgeek.com/wp-content/uploads/2019/12/power-rangers-beast-morphers-season-2-scaled.jpg?fit=2560%2C1440'/>
<LazyImage src='https://i1.wp.com/www.theilluminerdi.com/wp-content/uploads/2020/02/mighty-morphin-power-rangers-reunion.jpg?resize=1200%2C640&ssl=1'/>
<LazyImage src='https://m.media-amazon.com/images/M/MV5BNTFiODY1NDItODc1Zi00MjE2LTk0MzQtNjExY2I1NTU3MzdiXkEyXkFqcGdeQXVyNzU1NzE3NTg@._V1_CR0,45,480,270_AL_UX477_CR0,0,477,268_AL_.jpg'/>
</div>,
document.getElementById('app')
);.scroll-list > * {
margin-top: 400px;
}
.img-placeholder {
content: 'Placeholder!';
width: 400px;
height: 300px;
border: 1px solid black;
background-color: silver;
}<div id="app"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
This code is having them load as soon as the placeholder is visible on the screen, but if you want a larger detection margin, you can tweak the rootMargin option of the IntersectionObserver so it starts loading while still slightly off screen.