In this tutorial I’ll show you how to improve the performance of your web pages by using in-view.js. This JavaScript library reports back when something has scrolled into the viewport and will help us dynamically load our images as they’re needed.
Performance Matters
Web performance matters, especially if your website targets developing countries where connections are slow and data plans are expensive. A few basic tasks we commonly undertake to improve our websites’ performance include minifying JavaScript files and stylesheets, “gzipping” assets, compressing image sizes, after which we’re pretty much all set. But are we?

The above inspector example shows a single page loading 24 images in a mobile-size viewport at regular 3G-speed. And as we can see, the page load is complete at around eleven seconds! This is really slow given that we’re dealing with a simple page with nothing but a few images and a stylesheet. The page is not yet polluted with ads, nor tracking scripts which usually add further ballast to the page.
Also worth bearing in mind, is that this is merely an emulation. It doesn’t even take into account the server setup, latency, and other technical hurdles. The performance could be meven worse in reality.
So how can we improve the page load performance?
Bottleneck
First up, we have a number of images. The reason our page loads slowly is because all the images are flooding in together upon initial page load. If you take a closer look at the the previous image, you’ll see this doesn’t happen in parallel: a couple of images only begin loading once others are being rendered, which bogs the page down as a whole.
If we have a large number of images on a single page, we can consider loading these images asynchronously and only when the user needs them. This enables the browser to complete loading the viewable page without needing to wait for all the images to be rendered, ultimately saving the user bandwidth.
Getting Started
To follow along, grab the index-starter.html from the repo. There’s also an accompanying css/styles-starter.css which you can use too.
To begin with, we need to replace the image sources with a really small image, preferably encoded into base64 to avoid any extra HTTP requests. We use this image as a placeholder before we serve the actual image. That being said, we must also store the actual image source in a custom attribute named data-src
.
<figure class="post__image"><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-src="./images/image-24.jpg" alt="" width="800" height="554"></figure>
Once you have done this and refreshed the page, you should find the images are currently blank and their dimensions are not necessarily what your final images should have.

So let’s fix the styles.
Retaining the Image Ratio
The images we want to use are set at 800 by 550 pixels. We’ll divide the image height (800px
) by the image width (500px
), and multiply this by 100%
. Use the result to set the padding top
of the pseudo-element of the image container. Lastly, we need to set the image position to absolute
and set the maximum height to 100%
, so it won't bolster the height.
figure { position: relative; } figure img { top: 0; left: 0; position: absolute; max-height: 100%; } figure:before { padding-top: 69.25%; // ( 554 / 800 ) * 100% }
At this point, the image dimensions should be correct. However, the real image source still resides in a custom attribute so the browser can’t actually fetch any images yet.

Our next step will be adding some JavaScript that will load the image.
Get the Image Loaded
Firstly, we need to load in-view.js to the page. As mentioned, this lightweight library (which is not dependant on jQuery or a core library like Waypoints) detects whether an element is inside or outside the browser viewport.
Now create a new JavaScript file where we will write our JavaScript and load it after in-view.js, as follows:
<script src="./js/in-view.min.js"></script><script src="./js/scripts.js"></script>
Methods and Functions
The in-view.js library exposes the inView()
function which takes a selector as the argument. In this case, we will pass the figure
element; the element that wraps the images. The reason we select the wrapper element is because we are going to add a couple of classes to perform style transitions–this is more easily done when the class is on the wrapper element rather than the image itself, hence:
inView('figure')
Next, we use the .on()
method to bind the element with the enter
event to check whether the element is within the viewport. In addition, in-view.js also exposes the exit
event which does the opposite; this detects when the element is out of the viewport.
inView( 'figure' ).on( 'enter', function( figure ) { var img = figure.querySelector( 'img' ); // 1 if ( 'undefined' !== typeof img.dataset.src ) { // 2 figure.classList.add( 'is-loading' ); // 3 // 4 newImg = new Image(); newImg.src = img.dataset.src; newImg.addEventListener( 'load', function() { figure.innerHTML = ''; // 5 figure.appendChild( this ); // 6 setTimeout( function() { figure.classList.remove( 'is-loading' ); figure.classList.add( 'is-loaded' ); }, 300 ); } ); } } );
The enter
event will trigger a function, which will does the following:
- Select the image within the
figure
. - Make sure that it has the
data-src
attribute. - Add
is-loading
to the wrapper,figure
, element. - Load a new image with the source retrieved from the
data-src
attribute. - Once loaded add the image to the container.
- And lastly, replace the
is-loading
class with theis-loaded
class.
We Wanna Get Loaded
As you can see from the above code, we have introduced two new classes is-loading
, and is-loaded
. We use the is-loading
class to add a spinner animation while the image is loading. We then use the is-loaded
class, as the name implies, to add the transition effect to the image when the image has been completely loaded.
figure.is-loaded img { animation: fadeIn 0.38s linear 1s forwards; } figure.is-loading { position: relative; } figure.is-loading:after { content: ''; display: block; color: #ddd; font-size: 30px; text-indent: -9999em; overflow: hidden; width: 1em; height: 1em; border-radius: 50%; margin: auto; position: absolute; top: 50%; left: 50%; margin-left: -0.5em; margin-top: -0.5em; transform: translateZ(0); animation: loading 1.7s infinite ease; } @keyframes loading { 0% { transform: rotate(0deg); box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em; } 5%, 95% { box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em; } 10%, 59% { box-shadow: 0 -0.83em 0 -0.4em, -0.087em -0.825em 0 -0.42em, -0.173em -0.812em 0 -0.44em, -0.256em -0.789em 0 -0.46em, -0.297em -0.775em 0 -0.477em; } 20% { box-shadow: 0 -0.83em 0 -0.4em, -0.338em -0.758em 0 -0.42em, -0.555em -0.617em 0 -0.44em, -0.671em -0.488em 0 -0.46em, -0.749em -0.34em 0 -0.477em; } 38% { box-shadow: 0 -0.83em 0 -0.4em, -0.377em -0.74em 0 -0.42em, -0.645em -0.522em 0 -0.44em, -0.775em -0.297em 0 -0.46em, -0.82em -0.09em 0 -0.477em; } 100% { transform: rotate(360deg); box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em; } } @keyframes fadeIn { 0% { opacity: 0; } 100% { opacity: 1; } }
Fallback
Hope for the best, but plan for the worst. So just in case JavaScript is somehow disabled (a rare case, but perfectly possible) we need to ensure the image will still be displayed. Use the <noscript>
element with the image source immediately pointing to the real image source.
<figure><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-src="./images/image-24.jpg" alt="" width="800" height="554"><noscript><img src="./images/image-24.jpg" alt="" width="800" height="554"></noscript></figure>
We are all set! Refresh the page, and if we inspect the network timeline in DevTools we can see the page speed is now significantly improved since we’re only loading what’s visible to the users.

The page load is now complete in only 1.95s at regular 3G-speed; more than a 500% speed improvement!
Wrapping Up
In this tutorial, we looked at how to improve page load by rendering images only when the user sees them. This method is popularly known as “lazy loading” and it can help your website performance enormously.
There are many JavaScript libraries and jQuery plugins which do this, so why opt for in-view.js? Personally, in-view.js has been the kind of script I’ve been looking for since it doesn’t try to do too much. It only handles one thing and does it well. This kind of library gives more control and greater flexibility.
For instance, not only can we can use in-view.js to perform lazy loading, but we can also use it for things such as performing infinite scroll, perhaps displaying a floating subscribe form when the user reaches the end of the page (take a look at the demo to see that in action), or creating a vertical timeline without having to pull in yet another JavaScript library. Let us know how you use it!
