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.