Redefining Lazy Loading With Lazy Load XT

About The Authors

Denis Ryabov is lead developer of Mobile Joomla!, the most popular way to display Joomla! websites on mobile, as well as RESS.io. He is also a physicist with …

More about Denis Ryabov & Ugur Kaner ↬

Email Newsletter

Weekly tips on front-end & UX.
Trusted by 200,000+ folks.

The total size of media elements on the average web page is increasing constantly. Yet, especially on mobile devices, performance bottlenecks remain, which stem from bandwidth issues, widely varying network latency, and limitations on memory and the CPU. You need solutions for better and faster browsing experiences that work across all devices and browsers. In this article Denis Ryabov & Ugur Kaner will discuss why they should and shouldn’t use Lazy Load, and how to implement it.

Lazy loading is a common software design pattern that defers the initialization of objects until they are needed. Lazy loading images started to become popular on the web back in 2007, when Mika Tuupola drew inspiration from the YUI ImageLoader utility and released a jQuery plugin.

Since then, it’s become a popular technique to optimize page loading and the user experience. In this article I will discuss why we should and shouldn’t use Lazy Load, and how to implement it.

Why Lazy Load?

Images make up over 60% of an average page’s size, according to HTTP Archive. Images on a web page would be rendered once they are available. Without lazy loading, this could lead to a lot of data traffic that is not immediately necessary (such as images outside of the viewport) and longer waiting times. The problem? Visitors are not patient at all. By lazy loading, images outside of the viewport are loaded only when they would be visible to the user, thus saving valuable data and time.

Lazy loading is not limited to images. It can be used on pages with complex JavaScript, iframes and third-party widgets, delaying the loading of these resources until the user actually needs them.

Why Not Lazy Load?

Lazy loading is not a silver bullet, and it is known to affect performance. For example, most lazy-loading implementations either don’t have a src attribute in the <img> tags (which is invalid syntax, according to the HTML5 standard) or point to a blank image (hello, spacer.gif). This approach requires duplicate <img> tags wrapped in <noscript> tags for browsers with JavaScript disabled (or with the NoScript plugin installed):

<img data-src="path" attributes /><noscript><img src="path" attributes /></noscript>

Fortunately, this duplication doesn’t increase the page’s size significantly when you enable Gzip compression. However, some search engines might not index your images correctly, because the <noscript> tag is not indexed within content, and the <img> tag outside of <noscript> is referring to a blank image. Currently, Google seems to eventually index lazy-loaded images, but other search engines are less likely to.

How Is Lazy Loading Implemented?

You might be overwhelmed by the number of lazy-load plugins out there. You might also think that implementing one is easy: Just monitor page scrolling (or resizing), and then set the src attribute when an image is visible. If only it were that easy. Many things come into play when building a solid solution that works on both desktop and mobile. So, how do you separate the signal from the noise?

  • Throttling. Checking the visibility of images after every interaction (even a tiny bit of scrolling) could compromise the page’s responsiveness. To ease that, implement some sort of throttling mechanism.
  • All your mobile are belong to us. There is no scroll event in the Opera Mini browser and some old feature phones. If you receive traffic from those devices, you should monitor and load all images directly.
  • Lazy load or automatic pagination? Some implementations check only whether an image is above the fold. If the page is scrolled down to the very bottom via an anchor (or the scrollTo method in JavaScript), then all images below the fold will begin to download, instead of only the images within the viewport. This is more a matter of automatic pagination because users will have to wait for the remaining images to load after an interaction.
  • Dynamic image insertion. Many websites use AJAX navigation nowadays. This requires a lazy-load plugin to support the dynamic insertion of images. To prevent a memory leak, any references to images that are not in the DOM (for example, ones that appear after an AJAX-based replacement of content) should also be removed automatically.

This list is certainly not comprehensive. We have many more issues to consider, such as the lack of getBoundingClientRect in old browsers, a change in orientation without an ensuing resize event on the iPhone, or the particular handling requirements of the jQuery Mobile framework.

Unfortunately, most plugins do not handle all of the above.

Lazy Load XT

We’ve been optimizing web performance on numerous screens for almost a decade now. Our project Mobile Joomla has been applied to over a quarter billion web pages and is still one of the most popular ways to optimize Joomla websites for mobile. Thanks to this, we’ve been lucky to witness the evolution of the web from desktop to mobile and observe trends and changing needs.

With our latest project, RESS.io, we’ve been working on an easy solution to automatically improve responsive design performance on all devices. Lazy loading became an integral part of the project, but we came to realize that current lazy-load implementations are insufficient for the growing needs of the modern web. After all, it’s not just about desktop, mobile and images anymore, but is more and more about other media as well, especially video (oh, and did I hear someone say “social media widgets”?).

We concluded that the modern web could use a mobile-oriented, fast, extensible and jQuery-based solution. That is why we developed one and called it Lazy Load XT.

Here are its main principles, which consider both current and future applications:

  • It should support jQuery Mobile out of the box.
  • It should support the jQuery, Zepto and DOMtastic libraries. Of course, writing the solution in native JavaScript is possible, but jQuery is a rather common JavaScript extension nowadays, and one of our aims was to simplify the transition from the original Lazy Load to Lazy Load XT. This makes jQuery an adequate choice. However, if you don’t want to use jQuery at all, read the “Requirements” section below for details on reducing the size of dependent libraries.
  • It must be easy to start. The default settings should work most of the time. Prepare the HTML, include the JavaScript, et voilà!

Include

Lazy Load XT requires jQuery 1.7+, Zepto 1.0+ or DOMtastic 0.7.2+. Including the plugin is easy and as expected:

<script src="jquery.min.js"></script>
<script src="jquery.lazyloadxt.min.js"></script>

<script>$.lazyLoadXT.extend({edgeY: 200});</script>

<style>img.lazy {display:none}</style>

Use

By default, the plugin processes all images on the page and obtains an image’s actual source path from the data-src attribute. So, the recommended snippet to place an image on the page is this:

<img class="lazy" data-src="path" [attributes] /><noscript><img src="path" [attributes] /></noscript>

From this snippet, it is clear why we’ve set img.lazy above to display: none: Hiding the image is necessary in case there is no JavaScript, or else both the original image and the placeholder would be displayed. If the src attribute of the <img> tag is not set, then the plugin will set it to be a transparent GIF using the data-uri attribute.

If you’re not worried about users who have disabled JavaScript (or about valid HTML5 code), then just load jquery.lazyloadxt.min.js and replace the src attribute in the images with data-src:

<script src="jquery.min.js"></script>
<script src="jquery.lazyloadxt.min.js"></script>
<img data-src="path" [attributes] />

Video

Lazy Load XT is available in two versions: jquery.lazyloadxt.js and jquery.lazyloadxt.extra.js. The latter includes better support of video elements, both <video> tags and ones embedded in <iframe> (such as YouTube and Vimeo).

Markup changes are similar to the above, and replacing the src attributes with data-src and post with data-poster is sufficient if you’re using them in a <video> element.

<script src="jquery.lazyloadxt.extra.js"></script>
<iframe data-src="//www.youtube.com/embed/[videocode]?rel=0" width="320" height="240"></iframe>
<video data-poster="/path/to/poster.jpg" width="320" height="240" controls>
   <source data-src="/path/to/video.mp4" type='video/mp4; codecs="avc1.42E01E, mp4a.40.2"'>
   <source data-src="/path/to/video.ogv" type='video/ogg; codecs="theora, vorbis"'>
</video>
<video data-src="/path/to/video2.mp4" width="320" height="240" controls>

Size

The size of the jquery.lazyloadxt.min.js file is 2.3 KB (or 1.3 KB Gzip’ed), and the size of jquery.lazyloadxt.extra.min.js is 2.7 KB (or 1.4 KB Gzip’ed). That’s small enough, especially compared to jQuery and Zepto.

Requirements

Even though Lazy Load XT requires jQuery, Zepto or DOMtastic, loading the full versions of any of them is not necessary. For example, DOMtastic requires only a minimal set of modules (attr, class, data, event, selector, type) for you to get a 7.9 KB file (or 2.7 KB Gzip’ed), bringing the total size of both DOMtastic and Lazy Load XT to just 4 KB (Gzip’ed).

Compatibility

We’ve tested Lazy Load XT in the following browsers:

  • Internet Explorer 6 – 11
  • Chrome 1 – 37
  • Firefox 1.5 – 32.0
  • Safari 3 – 7
  • Opera 10.6 – 24.0
  • iOS 5 – 7 (stock browsers)
  • Android 2.3 – 4.4 (stock browsers)
  • Amazon Kindle Fire 2 and HD 8.9 (stock browsers)
  • Opera Mini 7

Performance

We have tested Lazy Load XT’s performance on a page with one thousand images and are happy with the results: Scrolling works well even on old Android 2.3 devices.

We also successfully tested various iterations of Lazy Load XT on over one thousand websites for several months in our jQuery Mobile-based Elegance and Flat templates.

Options

The plugin’s default settings may be modified with the $.lazyLoadXT object:

$.lazyLoadXT.edgeY = 200;
$.lazyLoadXT.srcAttr = 'data-src';

Note that you may change this object at any time: before loading the plugin, between loading and when the document is ready, and after the event is ready. (Note that the last option doesn’t affect initialized images.)

Lazy Load XT supports a lot of options and events, enabling you to integrate other plugins or implement new features. For the full list and details, see Lazy Load XT’s GitHub page.

AJAX Support

If you use jQuery Mobile with built-in AJAX page loading, then the Lazy Load XT plugin will do all of the magic for you in the pageshow event. In general, you should run the code below to initialize images inside a container with AJAX-loaded content.

$(window).lazyLoadXT();

Or run this:

$('#ajaxContainer').lazyLoadXT();

Extending Lazy Load XT

Lazy Load XT can be extended easily using the oninit, onshow, onload and onerror handlers or the related lazyinit, lazyshow, lazyload and lazyerror events. In this way, you can create amazing add-ons.

Some examples can be found on the GitHub page, along with usage instructions. We’ll highlight just a few of them here.

Loading Animation

Customizing the image-loading animation is easy. By default, Lazy Load XT includes spinner and fade-in animations, but you can use any effects from the Animate.css project or any other.

Responsive Images

Lazy Load XT has two add-ons for responsive images. One is “srcset,” to polyfill the srcset attribute (and that should be renamed data-srcset):

<img data-srcset="image-hd.jpg 2x, image-phone.jpg 360w, image-phone-hd.jpg 360w 2x">

The second is “picture,” a polyfill for the <picture> tag:

<picture width="640" height="480">
   <br data-src="small320.jpg">
   <br media="(min-width: 321px)" data-src="medium480.jpg">
   <br media="(min-width: 481px)" data-src="large640.jpg">
   <noscript><img src="large640.jpg"></noscript>
   <p>Image caption</p>
</picture>

Page Widgets

Lazy Load XT makes it possible to lazy-load page widgets (such as Facebook, Twitter or whatever widget you like). Insert any HTML code in the page using the “widget” add-on when an element becomes visible. Wrap the code in an HTML comment inside of a <div> with an ID attribute, and give the element a data-lazy-widget attribute with the value of that ID:

<!-- Google +1 Button -->
<div data-lazy-widget="gplus" class="g-plusone" data-annotation="inline" data-width="300"></div>
<div id="gplus"> <!--

   (function() {
      var po = document.createElement('script'),
      s = document.getElementsByTagName('script')[0];
      po.type = 'text/javascript'; po.async = true;
      po.src = 'https://apis.google.com/js/platform.js';
      s.parentNode.insertBefore(po, s);
   })();

--></div>

If the data-lazy-widget attribute has an empty value, then the element itself will be used as a wrapper:

<div data-lazy-widget><!--
   
--></div>

Many other add-ons are available, too. They include infinite scrolling, support for background images, loading all images before displaying them (if the browser supports it), and deferring the autoloading of all images.

Is There A Silver Bullet?

Lazy loading images is not a standard browser feature today. Also, no third-party browser extensions exist for such functionality.

One might assume that the lazyload attribute in the “Resource Priorities” draft specification by Microsoft and Google would do it. However, it has another purpose: to set the background priority for a corresponding resource element (image, video, script, etc.). Thus, if your aim is to load JavaScript or CSS before images, that’s your choice. There is another killer attribute, postpone, which prevents any resource from loading until you set the CSS display property to a value other than none. The good news is that support for the lazyload attribute is in Internet Explorer 11. The bad news is that the postpone attribute has not been implemented yet.

We do not know when or if the draft specification above will ever be fully supported by the major browsers. So, let’s look at the solutions we have now.

Some people have attempted to solve the duplication of the <img> tag in <noscript> tags by keeping only the <noscript> part and processing it with JavaScript. Unfortunately, <noscript> has no content in Internet Explorer, and it is not included in the DOM at all in Android’s stock browser (other browsers may behave similarly).

An alternative would be to use the <script> tag, instead of <noscript>, like so:

<script>function Z(){document.write('<br ');}</script>
<script>Z();</script><img src="path" attributes />

So, <img> would be an attribute of the <br> tag and would transform <br> tags into <img data-src> at the document.ready event. But this method requires document.write and is not compatible with AJAX-based navigation. We have implemented this method in the script add-on for Lazy Load XT, but the standard way using data-attributes seems to be clearer.

Finally, Mobify has an elegant Capturing API (see the recent review on Smashing Magazine) that transforms HTML into plain text using the following code and then processes it with JavaScript:

document.write('<plaintext style="display:none">');

Unfortunately, this solution has drawbacks of its own: It is quite slow, and the browser might treat it as a JavaScript-based HTML parser. Also, combining this solution with AJAX navigation is not clear, and it is not guaranteed to work correctly in all browsers because the <plaintext> tag was deprecated in HTML 2. It actually doesn’t work in W3C’s Amaya browser and on some feature phones (such as Nokia E70). Nevertheless, these are edge cases, and you may use Mobify.js and Lazy Load XT simultaneously, although that is beyond the scope of this article.

Comparing Lazy Load Solutions

Both Lazy Load XT and the original Lazy Load are not the only solutions around. Below we compare most of the major existing solutions:

FeatureLazyLoad for jQueryLazy Load XTUnveilLazy (by Eisbehr)Responsive Lazy LoaderbLazyLazyload (by VVO) Echo

Current version1.9.31.0.51.3.00.3.70.1.71.2.22.1.31.5.0
DependenciesjQueryjQuery, Zepto or DOMtasticjQuery or ZeptojQueryjQuery
Size (Gzip’ed)1.19 KB1.31 KB (or 1.45 KB with extras)338 B1.45 B1.23 KB1.24 KB1.01 KB481 B
Skips images above the foldyesyesyesnoyesyesnoyes
Loading effectsyesyesyes (with custom code)yesyes (with custom code)yes (with custom code)nono
Responsive imagesnoyes (via plugin)yesnoyesyesyes (with custom code)no
Supports scroll containersyesyesnoyesyesnoyesno
Supports horizontal scrollingyesyesnonoyesyesyesyes
Throttlingnoyesnoyesnoyesyesyes
Lazy background imagesyesyes (via plugin)noyesnononono
Lazy <video> tagnoyesnononononono
Lazy iframesnoyesnononononono
Supports Opera Mininoyesnononononono

Conclusion

The total size of media elements on the average web page is increasing constantly. Yet, especially on mobile devices, performance bottlenecks remain, which stem from bandwidth issues, widely varying network latency, and limitations on memory and the CPU. We need solutions for better and faster browsing experiences that work across all devices and browsers.

While no single lazy-load standard exists so far, we welcome you to try Lazy Load XT, especially if lazy-loaded video or other media is an important part of your website’s functionality.

Download And Contribute

Bug reports, patches and feature requests are welcome.

Further Reading

Smashing Editorial (al, ml, mrn)