Skip to the content...

Welcome to the David Walsh Blog. I'm a MooTools, Dojo, jQuery, CSS, and PHP Web Developer located in Madison, Wisconsin, United States. Please contact me if I can make your experience on my website better.

Introducing MooTools LazyLoad

35 Responses »
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!

Discussion

  1. August 18, 2009 @ 7:16 am

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

  2. August 18, 2009 @ 8:35 am

    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. August 18, 2009 @ 10:37 am

    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. August 18, 2009 @ 1:49 pm

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

    Thanks for sharing, David.

  5. August 18, 2009 @ 2:12 pm

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

  6. rolf
    August 18, 2009 @ 5:36 pm

    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. August 18, 2009 @ 5:57 pm

    @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
    August 19, 2009 @ 2:56 am

    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
    August 19, 2009 @ 5:21 am

    @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
    August 19, 2009 @ 10:07 pm

    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
    August 20, 2009 @ 7:20 am

    Beautiful, even better than youtube!

  12. August 21, 2009 @ 2:47 am

    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. August 21, 2009 @ 8:09 am

    not working in google chrome

  14. August 21, 2009 @ 8:20 am

    tbela99: Because Chrome uses WebKit.

  15. claudio ferreira
    October 8, 2009 @ 8:29 am

    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. October 28, 2009 @ 1:19 pm

    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. October 28, 2009 @ 1:41 pm

    @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. November 18, 2009 @ 7:09 am

    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
    February 18, 2010 @ 5:42 am

    bug in the sample usage

    line 5: newLazyLoad => new LazyLoad

    well done

  20. February 26, 2010 @ 2:50 am

    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
    March 9, 2010 @ 5:48 pm

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

  22. kn33ch41
    June 8, 2010 @ 12:00 pm

    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. June 10, 2010 @ 1:15 pm

    @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. June 10, 2010 @ 1:18 pm

    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
    July 14, 2010 @ 5:26 pm

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

  26. July 28, 2010 @ 2:25 pm

    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
    August 5, 2010 @ 6:20 am

    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
    August 5, 2010 @ 11:32 am

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

  29. david salazar
    August 5, 2010 @ 11:32 am

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

  30. August 5, 2010 @ 12:22 pm

    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
    August 5, 2010 @ 12:36 pm
  32. August 5, 2010 @ 8:08 pm

    …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
      August 11, 2010 @ 11:37 am

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

    • August 11, 2010 @ 12:12 pm

      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. August 31, 2010 @ 5:07 pm

    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

Be Heard!

Share your thoughts with fellow developers of all skill levels! I want to hear from you!

Name*:
Email*:
Website:  
Wrap your code with <code> tags, f00!