Get Lazy: Making Performant Image-Heavy Sites
It’s 2019. The web is full of big splashy images and bandwidth is infinite. Right?
Wrong.
It is very easy to get lost in self-deception in your Aeron chair at your cushy office. You’re probably attached to WiFi or 4G/LTE capable of 50-100Mbit+. You’re probably developing against localhost. Everything feels lightning fast… to you.
Meanwhile your users are in densely populated cities, urban canyons, crowded subways with dodgy WiFi, countries where 4G costs extra, and dealing with a whole host of other curveballs. They are waiting a long time for your glacially slow site to load. They are frustrated. Their experience could not be more different to yours!
What can you do? There are plenty of strategies: tune your JPEG compression, use progressive encodings, use WEBP, lower resolution, offer multiple sizes with srcset, implement infinite scroll, and the list goes on. Today we’re going to focus on just one: lazy loading.
Why Lazy?
The concept of lazy loading is pretty straightforward.
Ordinarily the browser tries to load all of the page content immediately, often in no particular order, often in parallel. The usual behavior means that the browser can slam a small network connection loading images that are nowhere near the viewport at the expense of those the user wants to see right away!
On the other hand, lazy loading means only loading images which are currently visible or are about to become visible as the user browses the page. This approach alleviates the issues with the browser’s default behavior by prioritizing things the user will see soonest and thus results in a far better user experience.
How is it implemented?
Lazy loading requires a bit of JavaScript to implement. A basic implementation looks roughly like this:
- The src attribute is left off of <img> elements. The source URL is put into a data attribute like data-src instead. The browser doesn’t know what to do without an src and no image stampede happens upon page load.
- An event handler is attached some event that fires any time the viewport has changed.
- The event handler checks if the new viewport requires any images to be loaded. If so, it sets its src attribute using the information from the custom data attribute.
- The browser does its thing now that our <img> has an src and the user sees the image load.
Simple enough, but what event should we use to detect viewport changes?
Historically the event to latch onto for viewport changes has been onScroll. Unfortunately the sheer volume of events can bring the page to a crawl if the handler is not carefully implemented. Womp womp.
IntersectionObserver was introduced more recently to address this use-case. It allows the browser to notify a callback when an element becomes (or is about to become) visible in the viewport. Orders of magnitude less events to handle means less impact from inefficient event handling code. Hurray! As of writing (April 2019) about 75% of browsers support this feature, and there is a polyfill to deal with the other 25% (at the expense of performance of course).
It’s Easy!
It’s good to know how things work under the hood but in reality there is an NPM package for nearly everything these days. The vanilla-lazyload library will do all of the above (and more!) for you.
If you’re using Vue.JS, you can use vanilla-lazyload with our recently published Vue component, @volleytravel/vue-img-lazy.
Just npm install and use the <img-lazy> Vue component anywhere you’d ordinarily use an <img> tag. It accepts all the same attributes as a regular image tag does and the component takes care of the rest for you. Enjoy!