It’s not a secret for anyone that mobile Internet can be quite slow. The page loading speed is very important for mobile-oriented pages. And it’s not a secret either that Google is using the site speed as a SEO factor. Also, it is well-known that people tend to leave a web site if it does not open within 3 seconds. As you can see, there is a plenty of reasons to improve the page loading speed.
Improve Web Site Performance
There are a lot of ways to improve the performance of your web pages. But lowering the content size and the number of requests are probably the things you should do first. Or, second. The first step is to enable server-side traffic compression and caching, but those are trivial things which leave no space for creative approach.
If your pages are heavy, they probably have many beautiful images, and you don’t want to remove those as a way to improve the page loading speed. But how do you decrease your web page initial load time, downloaded content size and the number of requests – all in one, and without removing those nice pictures?
When a web page opens, visitors see only the first part of it. They don’t see anything below the fold. What if you could tell the browser not to load those images from server until it needs to display them? Those may be big screenshots or small icons – if you have a lot of them, they are all speed killers. And the solution is loading the images on demand, or, as they say, lazy-loading the images.
Handling Images – Typical Approaches
There are several approaches to lazy-loading images, but the general idea is always the same: you change your markup somehow to make below-the-fold images, which are not essential for the first load, hidden by default. Then, you handle the window.onscroll event in JavaScript. When the page is scrolled and an image is supposed to become visible, you do some magic to make the browser load it.
Before we start, here is an important note on the SEO side of lazy-loaded images: neither of the approaches described in this post provide proper images indexing. However, our recommended approach, unlike others, allows people with disabled scripts see the images. Also, it gives crawlers a chance to see the image if they ever bother to index background images. However, there is a separate solution for image indexing: you can use image sitemaps to force web crawlers index your images. So, first of all, you need to decide whether the images you are going to lazy-load are important for your web site, and whether image sitemaps are an acceptable solution for you. It all depends on your specific scenario, but the negative SEO effect of poor page performance may be more significant than that of the non-indexed images.
If you are still reading this, you seem to be serious about the lazy-loading approach. OK, good! So, here are the most common magic tricks commonly used when implementing lazy loading.
- Custom attributes to store the image URL.
In the HTML markup, use a custom attribute for the image url rather than putting it to the src attribute. Then set the src attribute from JavaScript to make the browser load the image. Let’s take a look at an example.
Let’s suppose, you have an image on your page:
<img src="/images/screenshot.png"/>
To make it lazy-loaded, you convert the above code line to this:
<img src="/images/blank.png" data-src="/images/screenshot.png"/>
The src attribute points to an empty image just to make the markup valid.
And when you need to display the image, your script simply assigns the value of the data-src (or whatever) attribute to the src attribute. You get something like this at runtime:
<img src="/images/screenshot.png" data-src="/images/screenshot.png"/>
Is it a good solution? No. And here is why. Web crawlers and visitors with JavaScript disabled in the browser (some people say they do not exist, but we saw one of them last week) will never see your image if you do it this way. Besides, it might be against your markup creation rules to use custom attributes. Also, there is always a chance this will not work, because official support for custom “data-” attributes has only been introduced in HTML 5.
- Use invisible DIV elements.
Convert images to invisible DIVs and set the original image as a background. Then make those DIVs visible from JavaScript when needed. Let’s take a look into the details of this approach. So, it is sad enough that we cannot simply prevent a browser from downloading an image described like this:
<img src="..."/>
A number of experiments have been made to prove this. However, if you set the image as a background for a DIV with the display:none style, this will work and browsers will not download the image! Let’s take a look at an example.
Let’s suppose, you have an image on your page:
<img src="/images/screenshot.png"/>
To implement lazy loading, you convert it to something like this (yeah, inline styles are no good, they are here just for the sake of simplicity):
<div style="display:none; background-image:url('/images/screenshot.png');width: 500px; height: 300px"> </div>
And when you need to display the image, you simply make the DIV element visible from JavaScript by changing the style. You get something like this at runtime:
<div style="display:block; background-image:url('/images/screenshot.png');width: 500px; height: 300px"> </div>
Now, it is just the standard HTML markup, no custom stuff. Do you think this solution is good? Well, no. Same problem as before: your image is not accessible without JavaScript. Besides, if you check such page with Google PageSpeed Insights, you will see that it still considers all the negative page speed effect caused by large images, just as if they were loaded in the very beginning. At least, it did behave this way at the moment when this article was written. This means that even though your page will take less time to load in browsers, Google will not give you extra SEO points for this. Also, your page content will “jump” when the hidden DIV appears, because its original size is zero – not a good thing for visitors.
Handling Images – The Recommended Approach
So, we want everything at once:
- Improve the page load time in web browsers.
- Get better SEO ranks from Google.
- Make images visible with JavaScript disabled
Do you think we want too much? Nah, it is all possible! To accomplish this, we will need to slightly modify approach #2. Below, are the specific steps to follow.
- Prepare your content.
Convert an image to a DIV with a background. That is, get this code line:
<img src="/images/screenshot.png"/>
Convert it to this form:
<div class="lazyImg" style="background-image:url('/images/screenshot.png');width: 500px; height: 300px"> </div>
Note: we did not specify the display:none style, and we did not declare the lazyImg CSS class anywhere. This makes the image visible by default, if no script runs.
- Declare the lazyImg class dynamically.
Add the following script tag to the end of your page’s <head> section:
<head> <!--The original content of the head tag goes here--> <script type="text/javascript"> document.write("<style>.lazyImg{background-image:none !important;}</style>"); </script> </head>
Web pages are compiled from top to bottom, so this will guarantee that the image will not be loaded for people having JavaScript enabled. Unlike the original approach, Google PageSpeed will honor it too. Also, the space intended for the image will be occupied by the placeholder DIV element, so your page content will not “jump” when the image is loaded.
Important: Do not apply the display:none style to the DIV. If you do this, the code below (for image visibility detection) will not work properly, because the offsetTop property of the element will be zero.
Basically, that’s it for the creative part. But to make the solution complete, let us also implement the JavaScript logic which is similar for any of the approaches we mentioned above.
- Handle the window.onscroll event.
Now, we need to handle the window.onscroll event to show the image dynamically. To do this, we are going to use the approach described here:
isScrolled = false; window.onscroll = function () { isScrolled = true; }; function onWindowScroll() { if (isScrolled) { isScrolled = false; … //Our lazy-loading code will go here } } setInterval(onWindowScroll, 100);
The idea is simple: by default, your browser will generate lots of onscroll events for a single smooth scroll action. So, we only set a flag in the onscroll event and check for the flag ten times per second. If the flag is true, we invoke our complex logic. This way, the complex logic will not affect your browser performance.
Warning: It is not that simple for mobile browsers. A number of experiments demonstrate that mobile browsers fire the onscroll event only when the scrolling ends, but not during the process, and there is no proper workaround for this. So, you will not see lazy-loaded images until you stop scrolling on mobile devices.
- Collect all lazy-loaded images.
Let us generate a list of all delay loaded images once in order not to check them on every scroll action. To do this, we will select all elements having the “lazyImg” class. Note that there can be more than one class applied to an element, so it is not just simple retrieval of elements by their class name.
function getElementsByPartOfClassName(partOfClassName) { var allTags = document.getElementsByTagName("*"); var res = []; for (var i = 0; i < allTags.length; i++) { if (allTags[i].className && allTags[i].className.indexOf(partOfClassName) > -1) { res.push(allTags[i]); } } return res; } lazyImages = getElementsByPartOfClassName("lazyImg");
- Check which images are visible above-the-fold.
Now, it is time to write some code into our window.onscroll handler.
function onWindowScroll() { if (isScrolled) { isScrolled = false; var scrollTop = window.pageYOffset ? window.pageYOffset : window.scrollTop ? window.scrollTop : 0; for (var i = 0; i < lazyImages.length; i++) { if (scrollTop + document.documentElement.clientHeight - lazyImages[i].offsetTop > 0) { lazyImages[i].className = lazyImages[i].className.replace("lazyImg"); } } } }
This is it! Now, it is time for some explanations:
– The scrollTop variable stores current scroll position in pixels, which indicates how far the browser window has been scrolled (retrieved in a cross-browser way).
– In the loop, we enumerate all lazy-loaded images and check whether the top border (offsetTop of the image) is higher that the bottom border of the visible area (scrollTop + window client height).
– If the image is in above the browser window bottom border (above-the-fold), we remove the lazyImg class from it. As the result, the original image specified in the markup is loaded as a background and the image is thus delay-loaded.
Of course, you can optimize the code – for example, remove handled images from the array or use a more complicated markup/CSS rules to handle multiple elements at once. But the sample above gives you the general idea.
Lazy-Loading Images – Full Sample Code
As a conclusion, here is the full code of the solution in the order in which it should appear on your web page:
<head> <!--The original content of the head tag goes here--> <script type="text/javascript"> document.write("<style>.lazyImg{background-image:none !important;}</style>"); </script> </head> <!--Some page content goes here...--> <div class="lazyImg" style="background-image:url('/images/screenshot.png’);width: 500px; height: 300px"></div> <!--Some more page content goes here...--> <script src="/delay-load.js"> </script>
And here is the content of the delay-load.js file:
function getElementsByPartOfClassName(partOfClassName)
{
var allTags = document.getElementsByTagName("*");
var res = [];
for (var i = 0; i < allTags.length; i++)
if (allTags[i].className && allTags[i].className.indexOf(partOfClassName) > -1)
res.push(allTags[i]);
return res;
}