Improve site speed by Lazy loading images in web app
We create an Angular directive using Intersection observer API to delay loading of the images until they are needed and add some shimmer effect to improve the UX.
We spend a lot of time building new apps be it Single Page App (SPA) or Progressive Web Application (PWA) or websites using React, Vue or Angular. One such application was heavy on images on the home page. Images surely enhance the aesthetic appeal of the website but increase the site load time drastically. The browser has to fetch all the images from their source to complete loading of the page. Unfortunately, the browser will also load those images that are “below the fold”, i.e. not in the view of the user.
Fetching all images in the first load of the site leads to a slow site load. If the site doesn’t load or becomes usable in a short span of time, your users may just walk away, yes, leave your site and never return again.
The average time it takes to fully load the average mobile landing page is 22 seconds. However, research also indicates 53% of people will leave a mobile page if it takes longer than 3 seconds to load.
–Google statistics
How do we solve the load time issue and without removing the images?
Lazy Loading Images refers to a set of techniques that defers the loading of images on a page to a later point in time, when those images are actually needed instead of loading them upfront. These techniques help in improving performance, better utilisation of the device’s resources and reducing associated costs.
All hail Lazy-loading!
Lazy, but ultimately gets the work done!
A website which lazy loads images will defer the loading of resources as long as they are not in the user’s view.. Instead it will load them as soon as the user scrolls and the image comes into the viewport. The technique of lazy loading can be applied to almost all the resources on a page but currently we just care about our “image”s!
Our Solution
Our trick behind lazy-loading is to not set the src attribute of your img element, instead store the image url elsewhere to be put into the src lazily when applicable.
We store the image url in the dataset attribute, lets say data-src=”https://my-image-url”. Later when user scrolls to the image, we will set the img.src to img.dataset.src
- To summarize:
- Take the original URL of an img and put it in data-src dataset
- Make sure the src attribute is blank else it will trigger image fetch
- Use Intersection Observer API to wait and observe for a callback when the image element is in viewport
- The callback takes whatever URL data is present in dataset.src and puts it in img.src thus triggering fetch.
- We also add some fancy shimmer effect to the img div so that it looks cool while our image is getting fetched
Strictly speaking, intersection observers are not absolutely required to solve this problem. Here is a way to do it in plain Javascript using clientBoundingRect instead:
https://gist.github.com/bb1d1cca11238bd3d6a8a78500e3a6ce
A quote from Mozilla Developers Network:
Implementing intersection detection in the past involved event handlers and loops calling methods like Element.getBoundingClientRect() to build up the needed information for every element affected. Since all this code runs on the main thread, even one of these can cause performance problems. When a site is loaded with these tests, things can get downright ugly.
Here comes “The” Intersection Observer!
The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document’s viewport. Below we have another solution, in Javascript using Intersection Observers:
https://gist.github.com/3ea510f62228f02a0503b3a646fc1eba
This does the magic. Whenever the observer sees that the observed image is intersecting with the viewport, it fires the callback, which sets the image element’s src to the dataset.src.
We decided to take it a step further and build this into an angular directive for easy use in an SPA/PWA. The app where we needed it was an Ionic/Angular SPA!
The following snippet shows how an Angular directive can be built to lazy-load an image using Intersection Observer API
https://gist.github.com/1a0c46bc20f97aabbfede7d99f44dc81
Now using the above directive is quite straight forward:
https://gist.github.com/04b490099329eabf30493f986c4747e6
We have one more problem to solve! When the image loads lazily, depending on the size of the image, it can take some time to get downloaded and show. We want to show some nice placeholder during that time for a good user experience. We solve it using a CSS based shimmer effect which have become quite popular these days.
CSS shimmer snippet for image placeholder:
Just wrap your img in a div and put the shimmer class on that div.
https://gist.github.com/a5f4fd2bcb0005822c8542881879819a
Adding all pieces up, we have images with initial shimmering background. When it intersects with the viewport the src is changed, and fetch for real image is initiated. Till then, the image shimmers and later gets replaced by original image. Thus we have a website which loads smooth and looks awesome!
Browser support
Unfortunately, all browsers don’t support this awesome API. In our code above, we simply bail out and load the image immediately if the browser does not support the Intersection Observer API.
The browser support for Intersection Observer can be found here.
If you would like the solution to work on non supported browsers as well, you could use a polyfill: here.
The Author Nilarnab Mookherjee is a Software development engineer at Adrobit Technologies.
We specialize in custom built solutions for startups and helps them scale from idea to launch using our expertise and over 10 years of experience in software development. Checkout more details on our home page
One Response
nice
Comments are closed.