Lazy load images in Preact using Intersection Observer

Ashok Vishwakarma
Ashok Vishwakarma

Mar 10, 2019 • 2 min read

We love Preact for its tiny size and capabilities which makes it an ideal choice for smaller web applications.

Intersection Observer

Making this visibility test more efficient is what IntersectionObserver was designed for, and it’s landed in Chrome 51 (which is, as of this writing, the beta release). IntersectionObservers let you know when an observed element enters or exits the browser’s viewport.

To know more about Intersection Observer please check out the awesome article written by Surma on Google Web Fundamentals

IntersectionObserver’s coming into view - Chrome Developers

IntersectionObservers let you know when an observed element enters or exits the browser’s viewport.

developers.google.com

Which browsers are supported

Most of the modern browsers support Intersection Observer out of the box.

Update: Check latest support details

Can I use... Support tables for HTML5, CSS3, etc

"Can I use" provides up-to-date browser support tables for support of front-end web technologies on desktop and mobile web…

caniuse.com

If you still need support for older browsers and IE there is a well written and documented polyfill available on Github in the w3c repo, find the link below.

IntersectionObserver/polyfill at main · w3c/IntersectionObserver

Intersection Observer. Contribute to w3c/IntersectionObserver development by creating an account on GitHub.

github.com

The Image Component in Preact

To enable lazy loading for images the best approach is to create an Image component in Preact and use the same instead of the img tag.

import { h, Component } from 'preact';
import classy from 'classnames';

export default class Image extends Component {

state = {
src: 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7',
dataSrc: false,
loaded: false
}

inview(entries, observer){
entries.forEach(entry => {
if(entry.intersectionRatio){
entry.target.addEventListener('load', this.loading.bind(this));
entry.target.src = entry.target.getAttribute("data-src");
observer.unobserve(entry.target);
}
})
}

loading(event){
if(event.target.complete) this.setState({
loaded: true
});
}

componentDidMount(){
this.setState({
dataSrc: this.props.src,
loaded: false
});

const observer = new IntersectionObserver(this.inview.bind(this));

observer.observe(this.element);
}

render() {
return (
<div className="image-container">
<div className={classy({loader: true, hidden: this.state.loaded})}>
<img src="assets/img/loading.svg" />
</div>
<img src={this.state.src} data-src={this.state.dataSrc} ref={element => this.element = element} />
</div>
);
}
}

SCSS for .image-container

.image-container{
position: relative;

.loader{
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);

&.hidden {
display: none;
}
}
}

Uses of the Image component

<Image src={data.image} />

How does it work?

The following happens when you use the Image component instead of the img tag.

  1. Image component take original Image URL in src props
  2. Image component replace the original src with data-src
  3. IntersectionObserver initiates in componentDidMount and uses the inview method as a callback.
  4. IntersectionObserver observes the element which is .image-container
  5. If .image-container is in-view it sets the state with loaded property to true
  6. Which shows the image and also hide the loader.

Conclusion

Lazy loading can make the app load fast and provide a better user experience.


Ashok Vishwakarma

Ashok Vishwakarma

Google Develover Expert — WebTechnologies and Angular | Principal Architect at Naukri.com | Entrepreneur | TechEnthusiast | Speaker