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.
JavaScript
x
12
12
1
function sample() {}{
2
const [items, setItems] = useState([])
3
const [imgLoading, setImgLoading] = useState(true) // imgLoading might have to be boolean[]
4
useEffect(() => {
5
axios.get(url).
6
.then(response => setItems(response.data))
7
}, [])
8
return (
9
items.map(item => <img src={item.imageUrl} onLoad={setImgLoading(false)} />)
10
)
11
}
12
Advertisement
Answer
There are libraries for this, but if you want to roll your own, you can use an IntersectionObserver
, something like this:
JavaScript
1
33
33
1
const { useState, useRef, useEffect } = React;
2
3
const LazyImage = (imageProps) => {
4
const [shouldLoad, setShouldLoad] = useState(false);
5
const placeholderRef = useRef(null);
6
7
useEffect(() => {
8
if (!shouldLoad && placeholderRef.current) {
9
const observer = new IntersectionObserver(([{ intersectionRatio }]) => {
10
if (intersectionRatio > 0) {
11
setShouldLoad(true);
12
}
13
});
14
observer.observe(placeholderRef.current);
15
return () => observer.disconnect();
16
}
17
}, [shouldLoad, placeholderRef]);
18
19
return (shouldLoad
20
? <img {imageProps}/>
21
: <div className="img-placeholder" ref={placeholderRef}/>
22
);
23
};
24
25
ReactDOM.render(
26
<div className="scroll-list">
27
<LazyImage src='https://i.insider.com/536a52d9ecad042e1fb1a778?width=1100&format=jpeg&auto=webp'/>
28
<LazyImage src='https://www.denofgeek.com/wp-content/uploads/2019/12/power-rangers-beast-morphers-season-2-scaled.jpg?fit=2560%2C1440'/>
29
<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'/>
30
<LazyImage src='https://m.media-amazon.com/images/M/MV5BNTFiODY1NDItODc1Zi00MjE2LTk0MzQtNjExY2I1NTU3MzdiXkEyXkFqcGdeQXVyNzU1NzE3NTg@._V1_CR0,45,480,270_AL_UX477_CR0,0,477,268_AL_.jpg'/>
31
</div>,
32
document.getElementById('app')
33
);
JavaScript
1
11
11
1
.scroll-list > * {
2
margin-top: 400px;
3
}
4
5
.img-placeholder {
6
content: 'Placeholder!';
7
width: 400px;
8
height: 300px;
9
border: 1px solid black;
10
background-color: silver;
11
}
JavaScript
1
4
1
<div id="app"></div>
2
3
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
4
<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.