Faster page loads with image lazy loading

April 14, 2011

This post originally appeared on SlideShare's engineering blog.

Almost all traffic on slideshare goes to one type of page: the "view" page (the page where a document is displayed). As a result, we spend a lot of energy trying to tune the code that generates that page. Any decrease in page load time that we can get directly improves the user experience for all our users. Recently we were able to make big improvements by changing the way images load. This blog post will describe how we did it.

A large percentage of our page load time is taken up by images downloading. We'd already done a lot of work to reduce this (for example, by setting far-futures expires on many images, and by spriting images together to reduce the number of images downloaded). But the unique images for document thumbnails and profile pictures was not spritable, and far-future-expiry was unlikely to have much impact (since the images are usually unique to a given document). What to do?

After thinking about it a while, we realized that many of these profile and thumbnail images are below the fold in our current design, and therefore don't need to be included in the initial pageload. Postponing loading of these images sped up the page a lot. By loading these images only when the user scrolls to those locations, we can avoid loading of these images unless the user is viewing them, saving a lot of bandwidth and speeding up the page quite a bit. This technique is called image lazyloading.

We implemented this on slideshare in february and noticed marked improvement in performance.
On one of our monitoring endpoints for a popular page

Average Size Images
Before Lazy load 497kb 1702
After Lazy load 445kb 942

On another of our endpoints for another page

Load Time Requests Bytes in
Before Lazy load 24.9s 123 2310kb
After Lazy load 13.2s 85 787kb

To implement lazy load, we have used the jQuery lazy load plugin.

So without much ado, here is the code.

<script type="text/javascript">
ss = <%= {:userPlaceholder => User::PLACEHOLDER_IMAGE}.to_json %>

<% comments.each do |comment| %>
 <li class="j-lazyload-comment comment">
   <span class="userSummary">
      <img src="<%= User::PLACEHOLDER_IMAGE %>" original-src="<%= comment.user.image %>"/>
      <%= comment.user.summary %>
   <span class="commentText"><%= h(comment.displayText) %>
<% end %>

The above section of the rails view generates the list of comments for the slideshow. Notice that we are populating the src attribute of the image tags with a placeholder image and setting a custom attribute 'original-src' with the actual image. This is resource url that the jQuery lazy load plugin will use.

And the javascript for the lazy load is

 $("li.j-lazyload-comment img").lazyload({
   effect: 'fadeIn', 
   placeholder: ss.userimage_placeholder

For the related slideshows section which has a list of thumbnails scrollable in a container, we use the following code

 $("li.j-lazyload-related img").lazyload({
   effect: 'fadeIn', 
   placeholder: ss.slideshow_placeholder, 
   container : '#relatedList'

As you see, it is quite easy to set up and get running. You could use the noscript tag to avoid lazyload and show the actual images for users who have javascript disabled. If you want more control over the behavior that what this plugin provides, you may be interested in waypoints.