Introducing LazyLoad 2.0

Written by David Walsh on June 29, 2011 · 55 Comments

While improvements in browsers means more cool APIs for us to play with, it also means we need to maintain existing code.  With Firefox 4's release came news that my MooTools LazyLoad plugin was not intercepting image loading -- the images were loading regardless of the plugin, much like it always had with WebKit-based browsers.  Intercepting had continued to work within Internet Explorer but IE's reign of dominance is dying.  Clearly I needed to bite the bullet and code LazyLoad to use custom data- attributes to store the real image path.

Note:  It was always apparent that using custom attributes would become a necessity, but I hated doing so.  The big issue is with this is that it completely bricks image viewing for browsers which don't support JavaScript.  Since popular demand is calling for the data- attributes, and browsers are moving toward loading images before JS can intercept them, this was the really the only option.

Enter LazyLoad 2.0.  This second generation of MooTools LazyLoad does introduce breaking changes but the class itself is more compact and dynamic.  Here's the new class:

var LazyLoad = new Class({

	Implements: [Options,Events],

	/* additional options */
	options: {
		range: 200,
		elements: "img",
		container: window,
		mode: "vertical",
		realSrcAttribute: "data-src",
		useFade: true
	},

	/* initialize */
	initialize: function(options) {
		
		// Set the class options
		this.setOptions(options);
		
		// Elementize items passed in
		this.container = document.id(this.options.container);
		this.elements = document.id(this.container == window ? document.body : this.container).getElements(this.options.elements);
		
		// Set a variable for the "highest" value this has been
		this.largestPosition = 0;
		
		// Figure out which axis to check out
		var axis = (this.options.mode == "vertical" ? "y": "x");
		
		// Calculate the offset
		var offset = (this.container != window && this.container != document.body ? this.container : "");

		// Find elements remember and hold on to
		this.elements = this.elements.filter(function(el) {
			// Make opacity 0 if fadeIn should be done
			if(this.options.useFade) el.setStyle("opacity",0);
			// Get the image position
			var elPos = el.getPosition(offset)[axis];
			// If the element position is within range, load it
			if(elPos < this.container.getSize()[axis] + this.options.range) {
				this.loadImage(el);
				return false;
			}
			return true;
		},this);
		
		// Create the action function that will run on each scroll until all images are loaded
		var action = function(e) {
			
			// Get the current position
			var cpos = this.container.getScroll()[axis];
			
			// If the current position is higher than the last highest
			if(cpos > this.largestPosition) {
				
				// Filter elements again
				this.elements = this.elements.filter(function(el) {
					
					// If the element is within range...
					if((cpos + this.options.range + this.container.getSize()[axis]) >= el.getPosition(offset)[axis]) {
						
						// Load the image!
						this.loadImage(el);
						return false;
					}
					return true;
					
				},this);
				
				// Update the "highest" position
				this.largestPosition = cpos;
			}
			
			// relay the class" scroll event
			this.fireEvent("scroll");
			
			// If there are no elements left, remove the action event and fire complete
			if(!this.elements.length) {
				this.container.removeEvent("scroll",action);
				this.fireEvent("complete");
			}
			
		}.bind(this);
		
		// Add scroll listener
		this.container.addEvent("scroll",action);
	},
	loadImage: function(image) {
		// Set load event for fadeIn
		if(this.options.useFade) {
			image.addEvent("load",function(){
				image.fade(1);
			});
		}
		// Set the SRC
		image.set("src",image.get(this.options.realSrcAttribute));
		// Fire the image load event
		this.fireEvent("load",[image]);
	}
});

Using LazyLoad is as simple as:

<script>
/* do it! */
window.addEvent("domready",function() {
	var lazyloader = new LazyLoad();
});
</script>

<!-- then in the body -->

<!-- in-page image format -->
<img src="/images/blank.gif" data-src="/images/102_1139.jpg" />
<img data-src="/images/102_1139.jpg" />

Changes to version 2.0 of LazyLoad include:

  • You may now fade images in upon load
  • Uses a customizable data- attribute to store the real image src.
  • The resetDimensions options is no longer available
  • Document size is calculated during scroll, as dynamic pages change in size frequently.
  • Class code size is much smaller.

Lazy-loading images will never go out of style, as it saves your server bandwidth and saves your users from load images that they never scroll to.  Adding a fade effect allows for images to be gracefully placed within the page.  Let me know if you have further ideas for LazyLoad, as it should only get better!

Comments

  1. oragif June 30, 2011

    does your img syntax is best for SEO?
    y dont keep img src= syntax and add class LazyLoad for working

    • Because if the SRC is set initially, Firefox, Chrome, and Safari will load the image regardless.

  2. data-src bad idea…

    • Is there a different attribute you’d recommend?

    • Well Google Images uses data-src to theur lazy lodad script too! so is not a bad idea ;D

    • The data-src problem for me in using this script in CMS with WYSIWYG they don’t use it. So i don’t need it any more with manual use. The second problem that old version work with error in opera, load page and hide image before scroll…

    • Again though, the “old” method will no longer work in Chrome, Safari, or Firefox.

    • R.I.P. LazyLoad :(

    • I hope you’re referencing the technique and not my class. If you want to keep the “old method”, you can simply change the realSrcAttribute to “src”.

    • realSrcAttribute=”src” useless and does nothing :)

  3. Hey David,
    The plugin is awesome and it was really easy to get it to work.
    I don’t think that the data-src is a bad idea! It will work in most recent browsers anyway! And SEO??? You still have the title and alt tags, don’t you?

    A quick question – don’t you think that it will be better if you use the container to get the elements in it and to watch it for scroll? Right now you always get all images in the DOM, but I don’t see the real sense into using a container, and then still getting all images in the dom.

    I made this small change to get it working the way I wanted :)
    this.elements = this.container.getElements(this.options.elements);

    Kind regards,
    Daniel

  4. Another David July 1, 2011

    For SEO and for those who don’t have Javascript enabled, don’t you think, it would be better to keep the real image src in the HTML and replace it by the blank image during the initialization of the lazyload (and of course, storing the real src in an array).

    • That’s the problem — you can no longer rely on that technique. Safari and Chrome never respected it, and Firefox 4 stopped doing so. That technique simply isn’t viable anymore.

    • You can just use a simple additional Element which includes the normal img-Element

    • Sorry, the *noscript* has been deleted

  5. Leo Unglaub July 2, 2011

    Hi, thanks for the new version of this script. Works fine for me, but in FF4 if i use window i got the following error:
    Error: this.container.getElements is not a function
    Source: LazyLoad.js
    Line: 40

    If i switch to document it works fine.
    Thanks and Greetings from austria
    Leo

    • I made a big update to help that logic be more stable. Go ahead and update!

    • Leo Unglaub July 4, 2011

      Hi, the update works fine. Thanks for the Script. I have build an extension for the CMS Contao based on your script. I hope thats okay with you. It’s LGPL and your copyright is in :)

  6. Leo Unglaub July 8, 2011

    Hi,
    i think your script is so great that i developed an extension for the CMS Contao with contains your javascript. I hope this is okay for you. It’s LGPL and your Name and Copyright are untouched. You can find this here: http://www.contao.org/erweiterungsliste/view/lazy_load.html

    This works with every WISYWIG code and i think the people will love it.
    Thanks for your Work
    greetings Leo

  7. Hi, I’m trying to test lazyload so I can use it on my project but it doesn’t seem to be working properly. I’ve uploaded the test here but it just loads everything at once… http://dsgn.gs/lazyload/

    Cheers

  8. Thanks for the code, David. I appreciate it. I have a question though. That’s a pretty long chuck on javascript to put directly into my HTML. I made a new external jst file to try and clean up my code. It looks like this:

    window.onload = function() {
    var paras = document.getElementById('content').getElementsByTagName('p');
    if(paras.length) {
    paras[0].className = paras[0].className + ' intro';
    }
    };

    I’m not too experienced with javascript. Can you tell me what I’m doing wrong? Do I need that big chunk of code on every page? By the way, the more you can strip the demo code down the better. A lot of developers put extra styling and scripting in their html demos and it just makes for more digging that can be frustrating. That’s just me though.

    Thanks for sharing this with us,
    Sam

  9. Looks like I put some code in there that didn’t agree well with that comment. Basically I just took the code, put it in a js file, then did <script src= and the javascript file so everything was external.

  10. Have these changes been implemented in your WordPress plugin?

    http://wordpress.org/extend/plugins/mootools-image-lazy-loading/

  11. Really?

    Would you be interested in forking it, or should I go ahead and do it myself?

  12. Michael Knott August 9, 2011

    Hi, I’ve got the script up and working, but my problem is that images that I don’t put data-src on to show visibility:hidden on them. The only way I can get them to show is but adding data-src.

    Is there not a way to have some images load with lazy load and some just load like a regular image on the same page?

    ie:

    image1.jpg will not load at all, it only shows for a split second and then disappears. It just shows hidden and image2.jpg will load fine with lazy load.

    Thank you!

  13. Hi David,

    great script!

    I’ve just released a plugin for Joomla! with your script.

    http://joomla-extensions.kubik-rubik.de/jll-joomla-lazy-load

    Thank you very much for your work!

    Regards
    Viktor

  14. Hi David,

    We started using your script and it’s working quite well, we used it to load distant images from various webservers, it’s quite efficient. I just added a small patch around line 56 :

    var elPos = el.getPosition(offset)[axis];

    becomes

    var elPos = el.getPosition(offset)[axis] - this.container.getScroll()[axis];

    LazyLoad would then detect images within the range when pages are loaded with an offset (when reloading an already scrolled page for example).

    Greetings
    Julien

  15. about the internal loadImage function, I guess it will be better like this.

    loadImage: function(image) {
    image.addEvent("load", function() {
    if(this.options.useFade) image.fade(1);
    image.removeEvents("load")
    .removeProperty(this.options.realSrcAttribute);
    }.bind(this));
    }

    save memory and make html code clean, most of time, the realSrcAttribute and load event of image are used only once. by the way, nice job, David.

  16. Thanks for sharing this class! I added a bit of code to only target those images containing the “data-src” attribute. What do you think?


    var realSrcAttribute = this.options.realSrcAttribute;
    Slick.definePseudo('has-real-src-attr', function(){
    return Element.get(this, realSrcAttribute) != null;
    });

    this.elements = document.id(this.container == window ? document.body : this.container).getElements(this.options.elements + ':has-real-src-attr');

  17. CoreyRS April 11, 2012

    I’m trying to get this working inside a scrolling div rather than the entire window, but cant seem to get the container option to correctly set as the div class.

    Is it possible to do this with this plugin? I know the jquery version of lazyload has implemented this feature.

    • CoreyRS April 11, 2012

      Nevermind, cracked it :D

    • Mr.Nobody June 11, 2012

      Hello Coreyrs, please tell us how you did that.

      Thanks in advance.

  18. I tried to change the default container to a div… but unsuccessfully. I had to hack the Class editing the following code:

    Add an option:

    imgContainer: “divName”,

    Edit the line from

    this.elements = document.id(this.container == window ? document.body : this.container).getElements(this.options.elements);


    to

    // Elementize items passed in
    this.container = document.id(this.options.container);
    this.imgContainer = document.id(this.options.imgContainer);
    this.elements = document.id(this.imgContainer == window ? document.body : this.imgContainer).getElements(this.options.elements);

  19. I have an iPad application running as a webview. Damn developers who built it created a single webpage that on swipe shows and hides div elements to emulate page flips. Problem? There are over 300 images and crashing my third gen iPad. Darn retina. Do the hidden div elements load with lazy load automatically because they are technically there or do they not load until made active?

    Also is there the concept of lazy unload? Obviously when they get thru the entire app all images will be loaded and crash from having no memory left on the device. Nice if when a div hides you coulda load the image to free up the memory.

    Any advice/examples would be appreciated.

    JR

  20. One other item. What if most of my images are loaded thru CSS? Out of luck?

    Thanks.

    JR

  21. Hey, For some reason, the script just loads all images at once, instead of loading upon scrolling into view. I gave each image a height and width and still

  22. Nevermind, I fixed it. I had to add style=”display: block; visibility: hidden; opacity: 0;” to each image… Just like each image has on your live demo page…

  23. Your demo doesn’t work anymore!

  24. You can just use a simple additional noscript Element which includes the normal img-Element

  25. In Chrome it doesn’t work for me.

    At time of execution the LazyLoad script gets a Position of 40 for all images in my gallery-container. The img-Elements all have width and height set as attributes.

    This results in Chrome loading *all* images when loading the page.

    Is this issue known?

    • WebKit loads all images if the SRC attribute is set regardless of any plugin, so make sure true SRC’s aren’t set. My demo works in Chrome so maybe it’s a MooTools version issue?

  26. Farhan Farook February 21, 2013

    Excellent work when comparing other’s work. And if you do this plugin for jquery it’ll really helpful. Can you do this lazyload plugin for jquery library?

    Thanks in advance.

  27. Hello David, i want to ask. how to handle if image not found.

    Thanks

  28. hello the lazyload plugin for joomla is not w3c validated.
    the data-src attribute is not valid.
    lazyloadforjoomla / blank.gif “data-src =” http://www.xxxxxx.it/images/banners/image ….
    How do you solve this problem?
    Can you post the right code?

  29. zerouno August 3, 2013

    Hello,
    i have a script php for load image from folder because i have many image…., how to use lazyload for give effect to my gallery?

    Thanks.

  30. I fucking hate this bullshit lazy image wank. It seems to be infesting loads of sites, and like you say using lazy shite excludes those without JS. I surf without JS (if you have to ask why, you will not understand the answer) and I have noticed quite a few sites where they use this lazy crap to exclude content from those that are not willing to engage with the site’s JS abuses (mostly privacy attacks). IIRC Buzzfeed.com only uses this on “content”, the ads and junk that are not actual article content get served traditionally.

    I am currently working around the issue with Privoxy, making it edit shit web pages to put the “data” in the src field.

    FILTER: trans Bullshit image hiding
    s|(src=”[^"]+/trans.png”)(\ rel:bf_image_)(src=”http[^"]+”)| $3 |g
    s|pagespeed_lazy_src=([^>]+)src=[^>]+|src=$1|g
    s|]*data-original=([^>]+)([^>])*>||g
    s|]*data-href=([^>]+)([^>])*>||g
    s|]*data-lazy-src=([^>]+)([^>])*>||g

    The trans filter is then just applied to domains need it.

    > Lazy-loading images will never go out of style, as it saves your server bandwidth
    > and saves your users from load images that they never scroll to.

    I couldn’t care less about server bandwidth, don’t fill pages up with huge images and other media guff if bandwidth is a concern!

    And here you are making the user experience worse for the user. The above has an assumption something along the lines that the user is connected to the web server via gigabit ethernet, and that images do not take time to load. If a web pages looks to have loaded it is awful for it to start loading again when scrolling down, especially considering that if there isn’t a gigabit+ link the user might end up looking at gaps on web pages, or stuff pops-up or otherwise jumps about.

    > Adding a fade effect allows for images to be gracefully placed within the page.

    Ewwww, that kind of shit just makes things even worse by making things feel slow.

    > Let me know if you have further ideas for LazyLoad, as it should only get better!

    dd if=/dev/zero of=web2.0

  31. Hello Ranty Raterson, So, let me understand, you want to you the web but one where everything is on your terms – good luck with that.

  32. Hi David Walsh!

    when I use the ajax lazyload not work, please help me

    Code here:

    window.addEvent("domready",function() {
    		var lazyloader = new LazyLoad({ 
    			range: 200, 
    			realSrcAttribute: "data-src", 
    			useFade: true, 
    			elements: '.bg_img img', 
    			container: window
    		}); 
    	});
    

Be Heard

Tip: Wrap your code in <pre> tags or link to a GitHub Gist!

Use Code Editor
Older
Managing Firefox Add-ons with Version Updates
Newer
Google+ Invites