Lazy loading is a process where you defer loading your non-essential scripts and media until after the page loads. With lazy loading, you don't load everything at once, however, you defer the loading of images and scripts until they are actually needed. This is essential if you're trying to improve the load time of your site.

In this technical article, I will show you the process that I used in order to vastly improve the performance of this blog site.

Let's take a second to visually see what lazy loading is by taking a look at my own site. If you navigate to my main blog at www.gregoryalexander.com/blog and slowly scroll down, you will notice that the images do not load until you reach them. When the image is in the viewport, you will notice an image that looks like it is fading in. I am not loading these images when the page loads, but am waiting for you to scroll down before loading them. In order to achieve this effect, we need to have a library that uses the intersection observer API .

This is how the 'big boy's', like Facebook do it. Follow along and I can walk you through in order to do it on your own.

There are a multitude of different lazy loading libraries out there. I have tried a handful of these libraries, but settled on defer.js by shinsenter. My requirements are more extensive than usual. Along with the my requirements to lazy load my blog media, I needed to defer the loading of Kendo UI and other extensive libraries such as the Green Sock Animation Platform (GSAP). These libraries need to have a strict order to which libraries are loaded that they depend upon.

I had some issues with a handful of other libraries, but defer.js allowed me to achieve what I need.

Kendo UI needs to have jQuery loaded prior to the extensive Kendo UI scripts, and Kendo UI requires loading a large javascript library along with common .css files, a .less based css file that is required by the users chosen theme, and mobile css for mobile clients. GSAP also has several dependencies. I'll go through the process that I used to properly defer these scripts until the page loads defer.js below. At the end of this technical article, I'll discuss how I lazy loaded the blog media as well.

Before we do anything, we need to insert the defer.js code in the head section of your document:

view plain about
1//* Script to defer script resources. See https://appseeds.net/defer.js/demo.html.
2// @shinsenter/defer.js */

3!function(e,o,t,n,i,r){function c(e,t){r?n(e,t||32):i.push(e,t)}function f(e,t,n,i){return t&&o.getElementById(t)||(i=o.createElement(e||'SCRIPT'),t&&(i.id=t),n&&(i.onload=n),o.head.appendChild(i)),i||{}}r=/p/.test(o.readyState),e.addEventListener('on'+t in e?t:'load',function(){for(r=t;i[0];)c(i.shift(),i.shift())}),c._=f,e.defer=c,e.deferscript=function(t,n,e,i){c(function(e){f(0,n,i).src=t},e)}}(this,document,'pageshow',setTimeout,[]),function(u,n){var a='IntersectionObserver',d='src',l='lazied',h='data-',p=h+l,y='load',m='forEach',r='appendChild',b='getAttribute',c=n.head,g=Function(),v=u.defer||g,f=v._||g;function I(e,t){return[].slice.call((t||n).querySelectorAll(e))}function e(s){return function(e,t,o,r,c,f){v(function(n,t){function i(n){!1!==(r||g).call(n,n)&&(I('SOURCE',n)[m](i),(f||['srcset',d,'style'])[m](function(e,t){(t=n[b](h+e))&&(n[e]=t)}),y in n&&n[y]()),n.className+=' '+(o||l)}t=a in u?(n=new u[a](function(e){e[m](function(e,t){e.isIntersecting&&(t=e.target)&&(n.unobserve(t),i(t))})},c)).observe.bind(n):i,I(e||s+'['+h+d+']:not(['+p+'])')[m](function(e){e[b](p)||(e.setAttribute(p,s),t(e))})},t)}}function t(){v(function(t,n,i,o){t=[].concat(I((i='script[type=deferjs]')+':not('+(o='[async]')+')'),I(i+o)),function e(){if(0!=t){for(o in n=f(),(i=t.shift()).parentNode.removeChild(i),i.removeAttribute('type'),i)'string'==typeof i[o]&&n[o]!=i[o]&&(n[o]=i[o]);n[d]&&!n.hasAttribute('async')?(n.onload=n.onerror=e,c[r](n)):(c[r](n),v(e,.1))}}()},4)}t(),u.deferstyle=function(t,n,e,i){v(function(e){(e=f('LINK',n,i)).rel='stylesheet',e.href=t},e)},u.deferimg=e('IMG'),u.deferiframe=e('IFRAME'),v.all=t}(this,document);

This script is short, and it can be either placed inline, or you can grab the code via CDN at https://github.com/shinsenter/defer.js/.

Defer Kendo UI.

Kendo UI requires loading the following files- in this order:
  1. jQuery (not deferred)
  2. kendoUiCore.js (or kendoUiAll if you're using your own license).
  3. The kendoUi common .css file.
  4. The kendoUi theme .css file.
  5. And the kendoUi mobile .css file

Here is how I achieved this in actual code. This code also needs to be placed in the head section of the page underneath the defer.js code:

view plain about
1// Load the javascript files.
2<script src="/common/libs/kendo/js/jquery.min.js"></script>
3<script type="deferjs" src="/common/libs/kendo/js/kendo.all.min.js"></script>
4// Load the style sheets
5<script type="deferjs">
6// Kendo common css. Note: Material black and office 365 themes require a different stylesheet. These are specified in the theme settings.
7$('head').append( $('&lt;link rel="stylesheet" type="text/css" />
').attr('href', '/common/libs/kendo/styles/kendo.common.min.css') );
8// Less based theme css files.
9$('head').append( $('&lt;link rel="stylesheet" type="text/css" />').attr('href', '/common/libs/kendo/styles/kendo.silver.min.css') );
10// Mobile less based theme file.
11$('head').append( $('&lt;link rel="stylesheet" type="text/css" />').attr('href', '/common/libs/kendo/styles/kendo.silver.mobile.min.css') );
12</script>

Note the deferjs in the script type. This is a keyword used by the defer.js library to defer these scripts until page load. Here, all of the Kendo UI resources are deferred other than the jQuery library.

Defer GSAP and ScrollMagic resources.

GSAP is an amazing library if you're interested in cutting edge animation effects. I use GSAP and ScrollMagic to provide for my parallax effects. See the entry Introducing Galaxie Blog for an example of a parallax effect.

My requirements to load GSAP and SrollMagic are:
  1. Load the tweenMax javascript library
  2. Load scrollMagic
  3. Load the main GSAP library
  4. Load the scrollToPlugin library
  5. And load my GSAP debugging script

All of the GSAP resources will be deferred until page load.
Here is the code:

view plain about
1<script type="deferjs" src="/blog/common/libs/greenSock/src/uncompressed/TweenMax.js"></script>
2<script type="deferjs" src="/blog/common/libs/scrollMagic/scrollmagic/uncompressed/ScrollMagic.js"></script>
3<script type="deferjs" src="/blog/common/libs/scrollMagic/scrollmagic/uncompressed/plugins/animation.gsap.js"></script>
4<script type="deferjs" src="/blog/common/libs/greenSock/src/uncompressed/plugins/ScrollToPlugin.js"></script>
5<script type="deferjs" src="/blog/common/libs/scrollMagic/scrollmagic/uncompressed/plugins/debug.addIndicators.js"></script>

Note: typically, the GSAP libraries are loaded at the end of the page, however, this did not work for me. I found that these files must be loaded in the head section.

Lazy loading media files.

My open source Galaxie Blog allows blog owners to upload pictures and media that show up on the top of every blog post. However, on the main blog page, this necessitates the client to download quite a lot of extra data. In order to achieve a decent page load time I had to figure out a way to delay the loading of this media. I also wanted to convey to the reader that something was happening and alert them with a subtle css effect when a new picture is loaded. Here is the defer.js approach:

CSS:

view plain about
1/* Lazy loading image classes */
2/* Initially hide the element with zero opacity */
3.fade {
4transition: opacity 500ms ease-in-out;
5opacity: 0;
6}
7
8/* Show it with the 'shown' class */
9.fade.shown {
10opacity: 1;
11background: 0 0;
12}


Javascript:
view plain about
1// Lazy loading images and media.
2// Callback function to add the 'shown' class into the element when it is loaded
3var media_loaded = function (media) {
4media.className += ' shown';
5}
6// Then call the deferimg and deferiframe methods
7deferimg('img.fade', 300, 'lazied', media_loaded);
8deferiframe('iframe.fade', 300, 'lazied', media_loaded);

Place the script and the CSS anywhere in your document as long as it is above the media that you're about to lazy load. Place the following javascript at the end of the page:

view plain about
1// Lazy load the images.
2deferimg('img.fade', 100, 'lazied', function(img) {
3img.onload = function() {
4img.className+=' shown';
5}
6});

Finally, include your image like so:

<img class="fade" data-src="https://gregoryalexander.com/blog/enclosures/8_31.png" alt="">

I'll quickly try to explain what is going on here. If you look carefully, you'll notice that the "src" tag is missing from the above image. The media does not show at first due to the src tag that is missing, and the opacity setting that is initially set to zero. When the defer.js library notices that the user has scrolled to the viewport where the image resides, defer.js takes the string found in the data-src tag and automatically builds the src dynamically. When the image is loaded to the client, the CSS briefly fades in.

You can use the same approach to defer and lazy load pretty much anything. If you're interested, you can dig into the meaty details by looking at my source code, or checking out the excellent defer.js demo.