Lazy Load Content and Widgets with MooTools and RequireJS

By  on  

The idea of lazy loading content is not new;  we've been lazy loading images (check out my MooTools LazyLoad plugin) for quite a while now.  And why wouldn't you lazy load content?  There are SEO advantages, load time advantages, bandwidth savings, and more, so including a bit of extra JavaScript could help your large website immensely.  In my latest redesign, I took the time to lazy load not just images but static content and social networking widgets to make my website's initial load happen faster to make user experience and Google rank improve.  Let me show you how I used MooTools and RequireJS to lazy load content.

So what am I lazy loading within my current design?

  • Twitter, Facebook, and Google Plus widgets for single blog posts.
  • Page footer;  since the footer content is out of sight during initial page load, it doesn't need to be loaded right away.
  • LighterJS Syntax Highlighter; if no PRE tag is on the page, there's no reason to grab the syntax highlighter.
  • Comment avatar images;  there's no point to loading these images immediately as they must be scrolled to.  If someone hits the "#comments" portion of the page, my LazyLoad plugin can handle loading the initial images.
  • Tweets;  tweet content is non-essential so they needn't be loaded right away.
  • MooTools StarRating;  again, only needed if it's a post page.

Of course, there needs to be a trigger for these items to load.  I've chosen three events to trigger loading of these components:  keydown, mousemove, and scroll.  Each of these events signify a human and "interested" presence.  Let me show you how I've implemented this lazy loading.

Load Initial Scripts

Not every script can be asynchronously loaded, so the scripts I do load synchronously are RequireJS and Google's JSAPI (for Analytics and Google Search):

<!-- JavaScript at the bottom for fast page loading --> 
<script src="http://ajax.cdnjs.com/ajax/libs/require.js/0.23.0/require.min.js"></script> 
<script src="https://www.google.com/jsapi?key=MY_KEY"></script> 
<script> 
	// Load MooTools, Google Search
	function loadScripts() { 
		require(["https://davidwalsh.name/wp-content/themes/klass/js/script.js"]); 
	}
	google.load("mootools", "1.3.0");
	google.load("search", "1");
	google.setOnLoadCallback(loadScripts);
</script>

Once the scripts are loaded, my theme's main JavaScript resources is required.

Lazy Loading Code

Since there are multiple tasks I want to run when the user "awakens," I create an onAwaken array that will hold each of the tasks.  A series of functions will be pushed to the array, each representing a sign task.  For example, the task which loads Twitter, Facebook, and Google +1 badges looks like:

// Create the array of awaken functions
var onAwaken = [];

// Get the share URL from this page via the canonical link
var cannons = $$("link[rel=canonical]"), shareUrl;
if(cannons.length) {
	shareUrl = cannons[0].get("href");
}

// If the PROMO DIV exists on the page
var promoDiv = document.id("promo");
if(promoDiv && shareUrl) {
	onAwaken.push(function(){
	
		// Create a SPAN and A for the Twitter share link
		var span1 = new Element("span",{ style:"display:inline-block;float:left;" }).inject(promoDiv);
		new Element("a",{
			href: "http://twitter.com/share",
			"class": "twitter-share-button",
			"data-count": "horizontal"
		}).inject(span1);
		
		// Inject the Twitter SCRIPT tag into the SPAN
		new Element("script",{
			src: "http://platform.twitter.com/widgets.js",
			async: true
		}).inject(span1);
		
		// Create the Facebook IFRAME
		new Element("iframe",{
			src: "http://www.facebook.com/plugins/like.php?href=" + shareUrl,
			scrolling: "no",
			frameborder: 0,
			allowTransparency: true,
			style: "border:none; overflow:hidden; width:500px;"
		}).inject(new Element("span",{style:"display:inline-block;"}).inject(promoDiv));
		
		// Create the G+1 Button
		var el = document.id(document.createElement("g:plusone")).set({
			href: shareUrl,
			size: "medium"
		}).inject(new Element("span",{style:"display:inline-block;"}).inject(promoDiv));
		
		// Get the G+ SCRIPT
		require(["http://apis.google.com/js/plusone.js"],function() {
			// console.warn("G+1 is ready!");
		});
		
		// Fade in all of the lazy loaded content
		promoDiv.fade(1);
	});
}

Note that a require call is within this function, which could probably be considering lazy loading during lazy loading.  In all seriousness though, it's important to note that entire resources are lazy loaded, not just execution of available functionality.

Another onAwaken function includes loading of my footer and right column content:

// Uses Moo to load and place content
function loadAndPlace(url,where,then) {
	new Request({
		url: url,
		onSuccess: function(content) {
			document.id(where).set("html",content);
			if(then) then();
		}
	}).send();
}

// Load footer and right column upon awakening
onAwaken.push(function() { 
	loadAndPlace(themePath + "footer.html","footer"); 
});
onAwaken.push(function() { 
	loadAndPlace(themePath + "follow-trends.html","contentRightColumn", function(){ 
		$$(".contentRightColumn").fade(1); 
	}); 
});

And then I also lazy load the MooTools StarRating plugin:

// Star ratings
var ratingForm = document.id("ratingForm"),
	themePath = "/wp-content/themes/klass/";
if(ratingForm) {
	// Get the post id
	var postId = document.id("postId").get("value");
	
	// Get star ratings
	require([themePath + "js/ratings/Source/moostarrating.js"], function() {
		// Set image vars
		MooStarRatingImages = {
			defaultImageFolder: themePath + 'js/ratings/Graphics/',
			defaultImageEmpty:  'star_boxed_empty.png',
			defaultImageFull:   'star_boxed_full.png',
			defaultImageHover:  'star_boxed_hover.png'
		};
		// Create star ratings
		var voted = false;
		var rater = new MooStarRating({
			form: ratingForm,
			radios: "rating",
			half: true,
			width: 16
		}).addEvent("click",function(value) {
			if(!voted){
				// Send request
				new Request({
					url: themePath + "rating.php",
					method: "post",
					data: {
						rating: value,
						id: postId
					}
				}).send();
				// Mark as voted
				voted = true;
			}
		});
		// Fix the value...wtf?!
		// Adding 0.5 because it's always showing up short 0.5 short.  WTF.
		rater.setValue((rater.currentIndex * 0.5) + 0.5);
		// Showtime
		ratingForm.setStyle("display","block");
	});
}

There is no limit to the number of functions you can fire when the user awakens, so let's assume I have a bunch more functions ready to run.  Now it's time to place JavaScript code into place to listen for the page's awakening.  Here it goes:

// Set up main register handler
var runEvents = ["keydown","mousemove","scroll"];
var runAwaken = function() {
	onAwaken.each(function(evt,index) {
		evt();
	});
	// Since we've run all items, let's remove the events
	runEvents.each(function(evtType){
		window.removeEvent(evtType,runAwaken);
	});
};
// Set registers in motion
runEvents.each(function(evtType){
	window.addEvent(evtType,runAwaken);
});

First we create an array of events we want the page to awaken on.  Next we create a function which will run upon awakening;  this function will run all awaken events and then remove the event from the window so that the runAwaken function will only run once.

The sky is the limit with the content you can lazy load.  I highly recommend evaluating your website for components which don't hold search engine value, or may speed up the load of your page (especially synchronous SCRIPT tags!).  As always, have fun with your websites and do what you can to make them load faster!

Recent Features

Incredible Demos

Discussion

  1. Good topic, and good plugin, very “all-encompassing”, for all content types.
    But how about lazy-loading images? Specifically in firefox?
    I wrote a plugin here that does this
    well for images in all major browsers.

  2. Dutchie

    Didn’t you use the :once pseudo event on purpose, because it requires more additional code than really necessary in your case (I mean the whole events.pseudos class etc), or is/was there another reason? (I mean, it’s a good opportunity to mention pseudo events hehe)

    I’ve used a similar technique, but after a while wondered if the “load content on mouse move” (the user wakes up idea) isn’t starting a too big delay sometimes that blocks the user for a sec. I mean, you experience a small hick up in the responsiveness of the page if you fade stuff in on multiple places and fire off some ajax requests, you know what I mean..?
    I haven’t come to a conclusion yet though, of course it depends on the case.

    Good post though.

    • This is why you only load none-essential items like sidebar widgets and the footer on awake, and fade them in.

    • Dutchie

      The fade causes the delay in response and also the users first reaction/response could be “hey what happened here just now, what did I miss?.. I’m just saying it should be applied carefully.

  3. Buri

    Nice post. One question, though: In loading twitter and fb you use new Element(), but when you load G+1, you do it with document.createElement(). Is there some specific reason?

    • Yes, because MooTools / Slick doesn’t play with elements of type “g:plusone”.

  4. Thats nice actually and isn’t something that I had really noticed before reading this which must mean it is effective.

    The interesting thing is that effectively your main page load is similar to the content you would normally expect in a print stylesheet where you would normally spend your time hiding the extraneous information around the edges.

  5. Ivan

    Mootools, requireJS… wtf…
    All this could be done with some short, simple, plain and cross-browser solution:

    setTimeout(function(){
    document.getElementsByTagName(“script”)[0].src = “test.js”
    },3000)

    (You need an empty tag at the beginning of the )

    And in that javascript you put all the things you need to check/insert.

    • How would you pull in the StarRating resources only if needed?

  6. Shaun

    Is there anything like this out there using jQuery rather than mootools? I guess I could figure it out myself .. but reinventing the wheel it’s my idea at this point :)

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