Faster page loads with image lazy loading
April 14, 2011
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 %>
</script>
<ul>
<% 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>
<span class="commentText"><%= h(comment.displayText) %>
</li>
<% end %>
</ul>
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
$(function(){
$("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
$(function(){
$("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.