With all the fancy JavaScript libraries to choose from these days, it can be hard to keep your website’s page weight down. Even if you’re using a small, sleek library like YUI or MooTools, there are often times when it would be nice to be able to load certain scripts only when they’re needed.
For example, if you load a bunch of fancy animation libraries to do something spiffy when the user clicks a button, but then the user does something else instead of clicking the button, then you’ve wasted precious time loading scripts you didn’t even need.
Lazy loading is when you wait to load something until you actually need it. It’s not a new concept, and the techniques for implementing lazy loading in JavaScript have been around for a while, but it can be a real pain in the ass to roll your own lazy loading solution, especially if you want it to be compatible with all the major browsers.
Luckily, I’ve done the hard work for you. All you need to do is include LazyLoad in any web page like so:
<script src="http://cdn.wonko.com/lazyload/1.0.4/lazyload-min.js"></script>
Then, when you need to load a script, call LazyLoad.load() or LazyLoad.loadOnce() and pass in the URL of the script you want to load (or an array of URLs if you need to load multiple scripts). You can also specify a callback function that will be executed when the scripts are finished loading.
The only difference between LazyLoad.load() and LazyLoad.loadOnce() is that loadOnce() checks to see if the scripts have already been loaded and makes sure nothing gets loaded twice.
// Called when all scripts have finished loading.
function loadComplete() {
alert('Hooray for LazyLoad!');
}
// Load the YUI Dom, Event, and Animation libraries if they haven't already
// been loaded.
LazyLoad.loadOnce([
'http://yui.yahooapis.com/2.5.2/build/yahoo-dom-event/yahoo-dom-event.js',
'http://yui.yahooapis.com/2.5.2/build/animation/animation-min.js'
], loadComplete);
If necessary, you can also have LazyLoad pass an argument to the callback function or execute the callback in a specific scope:
// Pass an argument to the callback function.
LazyLoad.load('http://example.com/foo.js', loadComplete, 'custom argument');
// Execute the callback function in the specified scope.
LazyLoad.load('http://example.com/bar.js', loadComplete, this, true);
For more usage details, see the source documentation or the unminified source code.
If you’d like to use LazyLoad, you can either include it directly from my server as demonstrated above, or you can download it and host it yourself:
- lazyload-1.0.4.tgz (2008-07-24)
- lazyload-1.0.4.zip (2008-07-24)
LazyLoad works in Firefox 2.x, Firefox 3.x, IE6, IE7, Safari 3.x (including iPhone) and Opera 9.x. If you run into any problems, please let me know.
Comments
Optimized for callbacks
Also, perhaps you should make the pending and loaded members objects in the form of { script_url : (true|false) } to allow for parallel processing from separate callers or iterative calls with separate callbacks. Example
Re: Optimized for callbacks
Technically it is possible to call
load()orloadOnce()without providing a callback (although, as you say, it could be optimized), but I'm not sure I want to encourage that style of usage. If you ever find that you're calling LazyLoad without providing a callback, that's a good indication that you either don't actually need LazyLoad or that you're living dangerously and will soon be bitten by race condition bugs.I also intentionally implemented LazyLoad in such a way as to make parallel calls impossible, since that would add all kinds of complexity for very little benefit. As it is, you can safely make several calls to
load()orloadOnce()and be assured that your callbacks will be executed in the order that they were provided. Parallel loading would make that order unpredictable and would put a greater burden on developers to check for race conditions.downloading or executing?
Are you loading (downloading) the script when an event is fired, or are you loading (browser execution) the script when an event is fired? And wouldn't either one slow down the dynamic of whatever it is that is suppose to happen?
Re: downloading or executing?
When you load a script with LazyLoad, the browser downloads and executes it.
Whether or not this results in a user-visible slowdown depends on when you do it. You definitely wouldn't want to wait to download a huge library until the exact moment you need it, but if you're creative, you can find ways to start loading the library a short time before you'll need it, such as when the user moves the mouse near a certain area of the page or carries out certain actions that will lead to a need for the library.
The idea is to move the delay out of the initial pageload (where it's very noticeable) and instead load things at a time when the delay is less noticeable or less of an inconvenience.
Order not guaranteed
That said, I think it would be silly to have a page peppered w/ LazyLoad calls.
I'm not sure if there are xbrowser (or moral) issues with this method, but you could probably avoid the setInterval by moving its anon function to a named inner function (say loadComplete) then
Full disclosure: I didn't test the method beyond POC :)
Re: Order not guaranteed
You're right, there is a potential for a setTimeout race condition. I'll implement a queue to fix that as suggested.
As for the second suggestion, I do have moral issues with abusing onerror like that, but there might be another way of achieving that behavior and avoiding the setInterval. I'll give it some thought.
Re: Order not guaranteed
I've released LazyLoad 1.0.1, which implements these suggestions. Thanks!
Callback problem
], lazyLoaded); Nice job, this class is really great! :)
cheers
Why body? Why not Head?
Yesterday i try to use LazyLoad. It seems good, but i saw that new script adds to document.body . Why You do that? Why you don't use HEAD element? I do some corrects in script, like that:
... document.getElementsByTagName('HEAD')[0] ... appendChild ...
So, if you really need to add new script to body, let me know why.
No title
I have the same question as Nazarov-- why did you decide to put the script in the body instead of the head?
Re: No title
Because
document.body.appendChild()is easier to type and faster to execute thandocument.getElementsByTagName('head')[0].appendChild()and because there's no benefit to putting the script in the head.No title
Thanks! I see. One more clarification-- as a test I'm calling a slightly modified version of LazyLoad from a bookmarklet. The only difference is at the bottom of my version I write:
var scripts = new Array('http://www.mysite.com/~kalvin/js/prototype.js', 'http://www.mysite.com/~kalvin/js/scriptaculous.js'); LazyLoad.load(scripts);
Unfortunately, Firebug shows that triggering this bookmarklet on any page results in multiple errors including "LazyLoad is not defined" and "Prototype is not defined." For the latter problem, it seems like the queue does not actually ensure that both files are loaded in order (Scriptaculous requires Prototype and can't find it.) For the LazyLoad problem, it seems like the inserted .requestcomplete() in the body is called before LazyLoad itself has loaded. I guess this is impossible since the insertion is done inside LazyLoad... any idea as to what might be going on?
Thanks so much for your help!
No title
Update-- it was an issue with the way Scriptaculous injects its own javascript files.
http://dev.rubyonrails.org/attachment/ticket/8722/scriptaculous-xslt-firefox.diff
fixed! Great script, thanks.
Small bug with IE
To fix, change line 87 from this:
if (this.readyState === 'loaded') {
To this:
if (this.readyState === 'loaded' || this.readyState === 'complete') {
Thanks!No title
Hi, wonko! I use your lazylib as transport layer in my own JS Loading Engine. It works good :) I will create OS project for my lib and i want to post a link to LazyLoad.js, and post it on download page. I need persist link to your last build. Can you give me that?
Thanks.
Preventing Op Aborted
Hey,
If you're getting the dreaded "Operation Aborted" error in Internet Explorer, you can get around this by changing the following:
Inside of load:if (!document.body || !document.body.firstChild) { window.setTimeout(function() { LazyLoad.load(urls, callback, obj, scope); }, 50); return; }This adds a listener that waits for document.body and document.body.firstChild. Once you have a body and a first child, you can change the document.body.appendChild calls to:
By not writing to the end of the DOM document (while it is potentially open), you can eliminate the "Operation Aborted" message.
Re: Preventing Op Aborted
Hi jakob,can you please explain better where I must put that code. thanks
Re: Re: Preventing Op Aborted
Hi daniel,
That statement can be put right at the top of the load() method in Ryan's script. All it does is make sure document.body and document.body.firstChild exist before trying to insert anything.
The second call can be found by looking for "appendChild" and changing the line as per the statement above.
We've come back to LazyLoad several times at Gaia, and are rolling it out for event based loading. When I have some examples / docs / etc I'll roll it out and link it here (or just hit me up via the contact form on my page and I'll let you know when I finish it.)
New to JS...
I am glad to have this function. I would like to call a JS file called “sites.js” and would like it to be completely loaded and then continue… I am not sure what is the Callback… How to do the coding? Thanks. [Pls don’t laugh. You guys are too proficient]
Script Loading Position
Hey Daniel, the script works GREAT for loading once the page has.
Quick question, I’m trying to load banner ad’s and the ad’s aren’t showing up in the position they’re called from. Have you run into this before and is there a solution for this?
Previous coment correction
Sorry, previous comment meant for Ryan.
Re: Script Loading Position
Do the ads use
document.write? That won’t play well with LazyLoad, since the loading occurs asynchronously (potentially after the rest of the page has finished loading) and the write doesn’t occur until the script actually executes.Override document.write
If you are having problems with document.write with LazyLoad, you can experiment with overriding the default document.write method. For example, I have used this in some POC code:
document.write = function(html) { with (arguments.callee) { var element = (typeof _documentWriteElement == "undefined") ? "body" : _documentWriteElement; } jQuery(element).append(html); };_documentElement would then be defined in the global scope and can be redefined in closures to override that default.
great
nice work! thanks!
Fann-tas-tic
Great script, thanks! Now my site is no longer a victim of slow external js-based widgets such as Woopra, UserVoice, etc.
Saved my day!
Found a problem. Can you help?
I have a callback function that adds an inline style and an anchor tag to the page. In MSIE, this has the unfortunate effect of only that inline style and a tag being loaded in the browsers.
What happens is this:
Page loads OK, then as soon as the lazyload for the script is completing loading, the page gets replaced with that inline style and anchor tag, everything else disappears.
Any ideas?
Will this work for SharePoint sites?
Or will I be required to add LazyLoad.loadOnce to the _spBodyOnLoadFunctionNames.pus h array?
Scriptaculous problem
For some reason, when I try to load Scriptaculous the page goes blank and appears to be trying to load something but never returns. I have the fix recommended by Kalvin but it didn’t make any difference. Anyone else have the same or similar problem?
Disable for Printing?
I love LazyLoad but it’s preventing images within my page from printing.
If I include:
$(function() { $("img").lazyload({placeholder : "_images/gray.gif", effect: "fadeIn"}); });then images never get printed.
Is there any method to disable LazyLoad during printing — something like:
onClick=“javascript:parent.p rint(); disableLazyLoad();”
?
Otherwise, it’s great! Thanks!
Re: Disable for Printing?
Mark, the jQuery image lazyloader plugin (which appears to be what you’re having trouble with) is not related to my LazyLoad library.
Oops!
Sorry mate! Wrong LazyLoad.
=)