Introducing MooTools LazyLoad

By  on  
David Walsh MooTools LazyLoad

Once concept I'm very fond of is lazy loading. Lazy loading defers the loading of resources (usually images) until they are needed. Why load stuff you never need if you can prevent it, right? I've created LazyLoad, a customizable MooTools plugin that allows you to only load images when the user scrolls down near them.

The MooTools JavaScript Class

/* lazyload */
var LazyLoad = new Class({
	
	Implements: [Options,Events],
	
	/* additional options */
	options: {
		range: 200,
		image: 'blank.gif',
		resetDimensions: true,
		elements: 'img',
		container: window
	},
	
	/* initialize */
	initialize: function(options) {
		
		/* vars */
		this.setOptions(options);
		this.container = $(this.options.container);
		this.elements = $$(this.options.elements);
		this.containerHeight = this.container.getSize().y;
		this.start = 0;
	
		/* find elements remember and hold on to */
		this.elements = this.elements.filter(function(el) {
			/* reset image src IF the image is below the fold and range */
			if(el.getPosition(this.container).y > this.containerHeight + this.options.range) {
				el.store('oSRC',el.get('src')).set('src',this.options.image);
				if(this.options.resetDimensions) {
					el.store('oWidth',el.get('width')).store('oHeight',el.get('height')).set({'width':'','height':''});
				}
				return true;
			}
		},this);
		
		/* create the action function */
		var action = function() {
			var cpos = this.container.getScroll().y;
			if(cpos > this.start) {
				this.elements = this.elements.filter(function(el) {
					if((this.container.getScroll().y + this.options.range + this.containerHeight) >= el.getPosition(this.container).y) {
						if(el.retrieve('oSRC')) { el.set('src',el.retrieve('oSRC')); }
						if(this.options.resetDimensions) {
							el.set({
								width: el.retrieve('oWidth'),
								height: el.retrieve('oHeight') 
							});
						}
						this.fireEvent('load',[el]);
						return false;
					}
					return true;
				},this);
				this.start = cpos;
			}
			this.fireEvent('scroll');
			/* remove this event IF no elements */
			if(!this.elements.length) {
				this.container.removeEvent('scroll',action);
				this.fireEvent('complete');
			}
		}.bind(this);
		
		/* listen for scroll */
		this.container.addEvent('scroll',action);
	}
});

Options for LazyLoad include:

  • range: (defaults to 200) The amount of space from the container's bottom position that you want to look for images to load.
  • image: (defaults to "blank.gif") The image to replace the original image.
  • resetDimensions: (defaults to true) Removes the image's width and height attributes.
  • elements: (defaults to "img") Images to consider for lazy loading.
  • container: (defaults to window) The container for which to look for images within.

Events for LazyLoad include:

  • onComplete: Fires when all images have been loaded.
  • onLoad: Fires on each individual image once it has been loaded.
  • onScroll: Fires when the container is scrolled.

The Sample Usage

/* very simple usage */
var lazyloader = new LazyLoad();

/* more customized usage */
var lazyloader = newLazyLoad({
	range: 120,
	image: 'logo.gif',
	resetDimensions: false,
	elements: 'img.lazyme'
});

Enhancements

This class isn't perfect -- it does not defer loading of images above the page's starting point. If you work on a website with a lot of anchor links and you'd like lazyloading of images above the fold, you'll need to add a bit to this class. Also note that due to WebKit bug #6656, Safari will continue to load the originally images despite the plugin.

Have any more suggestions? Share them!

Recent Features

  • By
    Write Better JavaScript with Promises

    You've probably heard the talk around the water cooler about how promises are the future. All of the cool kids are using them, but you don't see what makes them so special. Can't you just use a callback? What's the big deal? In this article, we'll...

  • By
    Send Text Messages with PHP

    Kids these days, I tell ya.  All they care about is the technology.  The video games.  The bottled water.  Oh, and the texting, always the texting.  Back in my day, all we had was...OK, I had all of these things too.  But I still don't get...

Incredible Demos

Discussion

  1. I thought you were working on a javascript lazy loading / load on request plugin, but this is nice too :D

  2. That’s great, I was looking for exactly this kind of moo script about a year ago and I dind´t had the time to make it myself.

  3. Hey David how are you, i’m a loyalty reader of your blog. i was watching your recent visit to monterrey mexico, i hope you like it i’m living near to this beautiful city. sii yaaa

  4. Three words… well, one really: Fan-frigging-tastic!

    Thanks for sharing, David.

  5. Thats usefull!!!
    Gonna take a deeper look on this for sure. Thanks man

  6. Rolf

    Why are you using filter on the elements? You already collecting all the correct elements using $$(elements)? Maybe I’m overlook something, it’s late ;)

    Anyway, nice class, I will have to compare it to the one I wrote a about a year ago (I can probably improve it here and there).. hence my question about using filter.

    Also, I added a threshold option to the class to set a minimum size of the image.. it’s pretty useless to apply a lazyloading to small images (it takes longer to do the JS than to display them)…….typing this now, I remember my own comment in the code that the size (width x height) could be set to 20×20 px even though the image size in bytes is 300kb (i should say dimensions instead of size)… so I dunno, it could be a valid addition, it depends on how the developer uses it (in my case it was for my own project, so I knew my 20×20 px images where also 1kb for example and should not have lazyloading applied to.

  7. @Rolf: Filtering is used to (a) determine which images can be loaded normally because they’re above the treshhold and (b) to remove images that have been scrolled-to so that the script is more efficient. Eventually the script shuts itself off when all images have been loaded. The width/height threshhold is a good idea!

  8. Truthseeker

    When you load the page and use END to jump to the bottom, the script loads all the images in the middle (atleast in firefox) before loading those that are in the view at the bottom. I wonder, is it possible to change that so it really loads only those in view or near?

  9. Rolf

    @David, oh jeez.. it was really too late yesterday ;) I saw “filter” and thought you was actually filtering on “images” and I was thinking why, because $$(elements) already grabbed all the right elements..

    I’ve always just do an each loop to create the correct collection of whatever you need. you now:

    var imagesToProcess = [];
    this.elements.each(function(element) {
       ...
       imagesToProcess.include(element);
    });
    

    Something like that you know, not using the array.filter method. ..i’ll acquaint myself more with array.filter ;) good tip.

    I like the idea to remove the event after processing it. Somehow I didn’t think of that and just stored a cached=true item to the image element (and later checking on that item/value). Ofcourse removing the event is cleaner.. another good tip.

  10. Ben

    Works nicely, but I haven’t really changed my opinion on this as a concept sorry David… lazy loading absolutely murders the user experience.

    I expect that when my browser tells me the page has finished loading, it’s not actually lying to me!

    Instead when I scroll down in what I believe is a completely loaded page, I have to wait for more stuff to load in – I haven’t interacted with any form controls or links or anything on the page that would indicate to me as a user that I might be in for some loading time… I just scrolled!

    I’ll ask the same question I asked last time you posted on this – if your users don’t want to see all that content, why is it there in the first place?

    I believe a better implementation is the “more” link at the bottom of a Twitter homepage – it serves the same purpose, but the user remains in control of loading extra content.

  11. Alex

    Beautiful, even better than youtube!

  12. I really love the stuff you post here….you were my entry to the world of javascript – thanks for all the good work :D

  13. not working in google chrome

  14. tbela99: Because Chrome uses WebKit.

  15. Claudio Ferreira

    Very interesing class David. I’ve been following your good work for some time now, specially with the Mootools community.

    About the lazy loading of images, it might be useful to check the imgElement.complete property before trying to lazyload. I mean, if it’s already completed loading (possibly cached) there’s no need to lazy load it.

    Anyway, tks for sharing this code.

  16. Well done. The jQuery version exists for a while, like others I was seeking one for the Mootools library, lazy to develop one flexible ;-) So thankssss ! Cheers from Japan.

  17. @Ben: Yes, unfortunately in Web 2.0 there are lots of misconceptions. But if I remembered lazy load concept was created to respond of websites with heavy pages and visitors who don’t read or scroll the whole content. You’re a developer. As the google survey reports, a few people really understand the difference between browser and search engine, that’s why Google recently launched http://www.whatbrowser.org/. So about understanding the meaning of “Page loaded”, I think you can cheat in a safely way with lazyload, and keep working with the members of your team, or take part in evangelizing to spread a user experience which doesn’t feet fashion. There is a lack of education, and for example in web agencies there are need to spread the clients “Web is not shopping, the most important result is how the users will use your website and how they will understand your company or your product” and spread some colleagues “Web 2.0 and user interface are not lightbox, sliders and lazyload” for examples. Keep trying evangelizing in a good way, it takes time, around the scale of years, and introduce or deeply develop what’s “user interface, usability, user experience, and user design experience”, inviting and supporting people to take part in. Good luck :-)

  18. Thank you very much, awesome script; I’ve found the jQuery version first, and I was more than glad to see there was a moo version too (developping a website using mootools right now).

  19. Vivian Gledhill

    bug in the sample usage

    line 5: newLazyLoad => new LazyLoad

    well done

  20. David, you script is great, 10x for sharing it I added it to the MyMaemo site http://mymaemo.com/ with a few modifications in order to fade in the elements.

    el.addEvent('load', function(event){
       this.fade('hide');
       this.tween('opacity', '1.0');
    });
    

    I am quite new to MooTools and if I managed to get it running somehow it seems like everyone could.

  21. Ryan

    I love the concept of lazy loading images but what about background images? Any thoughts there?

  22. kn33ch41

    Hey Dave–I’ve been doing research on lazyloading techniques over the last couple of days and have found that, well, the techniques that do not require the creation of a non-valid attribute on the img tag no longer work–your demo included.

    I’ve even written bare-bone code that essentially just finds all img tags on a page and blindly replaces their sources with a tiny placeholder image. The code is placed at the bottom of the HTML document before the closing body tag to get called once the DOM is loaded. However, the original sources for each img tag are still downloaded, despite having their sources changed.

    I read hundreds of comments on the jQuery lazyloading plugin–all of which early on expressed gratitude, but most recently only express disappointment that the technique no longer works.

    This should work. Your demo should work. It’s simple: replace the img sources on DOM load and the process is done. However, it does not work.

    I’m testing it with Firefox 3.6.3. I know none of the lazyloading techniques work with Webkit due to a bug. The lazyloading technique does work in Opera. It’s the only browser that defers the downloading of image resources.

    Any ideas why this technique suddenly no longer works in Firefox? Furthermore, any HTML-standard suggestions for making this work?

  23. @kn33ch41: I didn’t know about that. I started investigating too about applying lazy load concept with other elements like audio / video in a cross-browser way

    The way I’m trying is:

    * Define a array in javascript : window.mysite = { media:[]; }

    * Replace your media elements with a span and an id
    For example
    Lorem Ipsum

    * Just after or near it, pushing the element to the array and using a noscript tag:

    mysite.media.push({ url:'http://www.youtube.com/v/wdGHySpipyA&fs=1',id:'media-1'});
    

    * Then if flash is supported for example, I’m trying to active / generate a css, for example:

    .media-video {
    display: block;
    width: 480px;
    height: 385px;
    }
    

    * and then binding like lazy load so while scrolling, it can replace the target element with an image, or load a movie with swfobject.

    If i succeed doing that, then I’ll have to try hacking / extending WYSIWYG ( for example tinyMCE) so it can dynamically create the html code / js code as well as generating css from javascript so i can customize every media ( different with. height, etc…)

    If anyone has free time, feel free to go on, otherwise, keep in touch.

  24. my html code was stripped. I meant a “Lorem Ipsum span id=”media-1″ class=”media-video” and in the noscript add the object / embed code provided by youtube

  25. ameer

    hi david, it’s too bad your demo is not working on ipad :(

  26. Is it webkit’s fault that lazyload doesn’t work? Mashable.com employs a lazyload on their site and it seems to work fine for me in safari…

  27. Emmanuel Paris

    Great Tool.
    I’m using it in a WordPress blog.

    The only problem is that it disallows Lightbox effect.
    Even if the rel=”lightbox” is still here.

    Any idea how I could arrange this ?
    Thank You.

  28. David Salazar

    Why bother polluting the web with stuff that’s not supported on webkit.

  29. David Salazar

    Why bother polluting the web with stuff that’s not supported on webkit.

  30. Right, because if it doesn’t work in one browser engine it has absolutely no value. FYI — it not working in WebKit is a WebKit bug, not a plugin bug. And WebKit doesn’t even have 25% of the browser market.

    Why pollute my blog with useless comments?

  31. David Salazar
  32. …that one says:

    “Due to webkit bug #6656 Lazy Loading wont give you any improvements in Safari. It will load all images you wanted it or not.”

    • David Salazar

      I didn’t see that, but I did test it in safari and it does indeed work. Have you tried it?

    • That site’s lazyload works in Safari for me as well. Mashable.com also uses a jQuery-based lazyload that works in Safari as well.

      Any reason why a mootools one wouldn’t work in Safari?

  33. hi david, is is Great as the same as all your posts. thank you.

    i want to know if it is possible to control the images witch are using in a CSS file. not the img tags in a page.

    Best regards,
    Mohammad

  34. I have tried this plugin in mootools 1.3 and its giving me an error, so I have to switch back to 1.2.

    is it ok If I place this js code in bottom of the page or I have to place this in head of the page to make this work.

    Because I think, if I place this js code in bottom of the page then once html rendered it will make request to original image.

    What you think?

    • What error? You need to give me some detail dude.

    • NM, found the issue. Fixed and updated on the Forge.

  35. Steve Chan

    Cool.
    BTW: With the help of Mootools 1.3, now we can use pseudo scroll:pause for better experience :)

  36. I’ve installed on my WordPress and it function perfectly, but with Net panel of Firebug I’ve seen that there is always a request for a “blank.gif” file that doesn’t exist.
    The URL of the request is the same of the page, so it try to search for:

    site.com/blank.gif
    or
    site.com/category/example/blank.gif
    and so on.

    How can I set a default url?

    I’m using MooTools.js version 1.2.5

    • There’s an “image” option for you to change.

    • I know about image option, but I cannot set full url path of that image, I can only write the name of the file.

      For example, if I use the option: ” image: ‘http://cdn1.12n3.net/blank.gif’ ” the lazyload doesn’t function.

      But if I write only ” image: ‘blank.gif’ “, the gif is loaded from wrong inexistent location.

      Is there any chances to specify full url path?

  37. hello!
    first of all, thank you for this script. i like the idea to load images only when they’re really needed.

    for the webkit bug there could be a solution to bring it to work:
    usually there is this code in the script:
    el.store('oSRC',el.get('src')).set('src',this.options.image);
    if you place the img source inside a rel property the browser won’t ever load the image. so you can retrieve the img oSRC in the rel property and then put it as src…
    you can do now something like this:
    el.store('oSRC',el.get('rel')).set('src',this.options.image);

    i’ve tested your script on firefox4 (mootools.1.3.1) and it seems to work. if you have a list with over 300images to load, and you scroll very fast over the container the browser is getting little slow, but it still works. Surprisingly at some point the Browser suddenly crashes without any chance to grab an error…

    on webkit browsers the script works fine.

    got any idea??

    thx!!

    • Right, this would work for all browsers, but doesn’t degrade well in non-JS browsers.

  38. @ http://davidwalsh.name/mootools-lazyload#comment-19322
    @ http://davidwalsh.name/mootools-lazyload#comment-19318

    http://www.appelsiini.net/projects/lazyload does NOT work either in webkit browsers : if you inspect downloaded resources before any scrolling action (in Firebug’s Net panel for example), you can see all images are downloaded…

  39. Chris

    Like pointed out by someone else before, this script no longer works for modern browsers except Internet Explorer 9. I tested with Firefox 4, Opera 11, Chrome 11 and Safari 5 (yeah, I read about the supposed webkit error). Removing the src property via JavaScript does not stop any of these browsers from loading all images at once. If you don’t believe me, fire up a program like HTTP analyzer or HttpFox in Firefox or any debugging console with network monitoring.
    Again, like someone else said, the only way is to store the real image path in another property and then copy it to the src property when loading should happen. There’s another mootools plugin that does just that (moolazyloader). Unfortunately this way nothing is displayed with JavaScript disabled. So if you care about accessibility you have to create an a tag around the image pointing to the image and use its href property for the lazyload image src. That way without JavaScript you still cannot see the images directly, but at least you can access them.

  40. It is not working on WP 3.1?

    Any solution?

    • This isn’t a WordPress plugin. Have you downloaded the most recent version from GitHub or the Forge?

  41. No, Can I have the download Link? I am looking like a mashable had before!

  42. LazyLoad 2.0 will be released this weekend, so come back Monday to get it. :)

  43. Lazyload 2.0 will be work on wp?

  44. I can’t specify full path for blank.gif, if I use the option:
    image : 'http://site.com/blank.gif'
    the LazyLoad doesn’t function.
    But if I keep the option “image: blank.gif”, the gif is loaded from wrong location.

    Any chances to specify full url path for blank.gif?

    Thanks

  45. Nils Rückmann

    Hi,

    i’m getting an error on Mootools 1.4.3 (Lazyload 2.1):

    document.id(this.container == window ? document.body : this.container) is null

    line 40

    any idea ?

  46. Mariusz

    Hi,

    I’ve got a problem with applying this code to a certain div. I’ve tried replacing ‘container: window’ with ‘container: #mydiv’ ….but it doesn’t do the trick:( Could you help me with that please?

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!