WHAT IS THIS?

A JavaScript Preloader for HTML5 Apps

PxLoader is a Javascript library that helps you download images, sound files or anything else you need before you take a specific action on your site (like showing a user interface or starting a game). You can use it to create a preloader for HTML5 games and websites.

It let's you monitor download status by providing progress and completed events and it lets you prioritize the order in which items are downloaded. You can even tag groups of files and then prioritize downloads or register for events by tag.

We created PxLoader for the HTML5 version of Cut the Rope and out of the box works great with images and sound files (the types of resources we needed for the game), but it's designed to be extensible and work with any file type or other network action.

GET STARTED

Step by Step Instructions

1. Download the script and place the files in the JavaScript directory of your site. The files you need are:

  • PxLoader.js (the main library file)
  • PxLoaderImage.js (the image file downloader if you want to download images)
  • PxLoaderSound.js (the sound file downloader if you want to download sounds)*
  • PxLoaderVideo.js (the video file downloader if you want to download videos)
  • Any other downloaders that you write or get in the plugins section of this page

*The sound file downloader works with SoundManager 2 and allows it do the heavy lifting of the actual download. In fact, PxLoader always defers downloads to the downloader objects. That gives a complex sound library like SoundManager 2 the ability to choose how to download the file based on whether it's using Flash or HTML5 for the audio.

2. Add the core JavasScript file.

<script type="text/javascript" src="js/PxLoader.js"></script>

3. Add plugins for the resource types you need.


<!- images -->
<script type="text/javascript" src="js/PxLoaderImage.js"></script>

<!- sounds -->
<script type="text/javascript" src="js/PxLoaderSound.js"></script>

SAMPLE 1

Download Images Before Drawing to a Canvas

Images must be fully loaded before they can be drawn on an HTML5 canvas. In this example we wait for 3 images to download before drawing our picture.

Code


// Create the loader and queue our 3 images. Images will not
// begin downloading until we tell the loader to start.
var loader = new PxLoader(),
    backgroundImg = loader.addImage('images/headerbg.jpg'),
    treesImg = loader.addImage('images/trees.png'),
    ufoImg = loader.addImage('images/ufo.png');

// callback that will be run once images are ready
loader.addCompletionListener(function() {
    var canvas = document.getElementById('sample1-canvas'),
        ctx = canvas.getContext('2d');

    ctx.drawImage(backgroundImg, 0, 0);
    ctx.drawImage(treesImg, 0, 104);
    ctx.drawImage(ufoImg, 360, 50);
});

// begin downloading images
loader.start();

See it in action

SAMPLE 2

Report Progress While Images Load

HTML5 games typically download a large number of images. In this example, we'll report progress while the game loads 100 images. We've configured our server to delay each image by 1 second to simulate large images and so you can watch the progress. Don't worry, your browser will download batches of images in parallel so the demo won't actually take 100 seconds.

Code


// delay each image and append the timestamp to prevent caching
var baseUrl = 'http://thinkpixellab.com/pxloader' +
        '/slowImage.php?delay=1&time=' + new Date,
    $log = $('#sample2-log').val(''),
    $progress = $('#sample2-progress').text('0 / 100'),
    loader = new PxLoader();

// add 100 images to the queue
for(var i=0; i < 100; i++) {
    // this time we'll create a PxLoaderImage instance instead of just
    // giving the loader the image url
    var pxImage = new PxLoaderImage(baseUrl + '&i=' + i);

    // we can add our own properties for later use
    pxImage.imageNumber = i + 1;

    loader.add(pxImage);
}

// callback that runs every time an image loads
loader.addProgressListener(function(e) {

    // log which image completed
    $log.val($log.val() + 'Image ' + e.resource.imageNumber + ' Loaded\r');

    // scroll to the bottom of the log
    $log.scrollTop($log[0].scrollHeight);

    // the event provides stats on the number of completed items
    $progress.text(e.completedCount + ' / ' + e.totalCount);
});

loader.start();

See it in action

0 / 100

SAMPLE 3

Load Images in Groups

Many HTML5 sites need to download resources for several different sections of the app. PxLoader lets you tag and prioritize resources. For this example we'll assume that we need one set of images to draw a game menu and second set of images for the gameplay. Since we need to show the menu first we'll start the download for those images first. PxLoader can provide progress updates to many listeners and will scope the updates and statistics to only the set of tags a listener is interested in.

Code


// delay each image and append the timestamp to prevent caching
var baseUrl = 'http://thinkpixellab.com/pxloader' +
        '/slowImage.php?delay=1time=' + new Date,
    $log = $('#sample3-log').val(''),
    $menuProgress = $('#sample3-menuProgress').text('0 / 50'),
    $gameProgress = $('#sample3-gameProgress').text('0 / 50'),
    $totalProgress = $('#sample3-totalProgress').text('0 / 100'),
    loader = new PxLoader();

// queue 50 images for each section
var addImagesForTag = function(tag, $progress) {
    for(var i=0; i < 50; i++) {
        var imageUrl = baseUrl + '&i=' + i + '&tag=' + tag;
            pxImage = new PxLoaderImage(imageUrl, tag);
        pxImage.imageNumber = i + 1;
        loader.add(pxImage);
    }

    // add a listener to update progress for the tag
    loader.addProgressListener(function(e) {
        $progress.text(e.completedCount + ' / ' + e.totalCount);
    }, tag); // scope listener to the current tag only
};

addImagesForTag('menu', $menuProgress);
addImagesForTag('game', $gameProgress);

// listen to every event to update total progress
loader.addProgressListener(function(e) {

    // log which image completed
    var line = ' Image ' + e.resource.imageNumber +
        ' Loaded [' + e.resource.tags[0] + ']\r';
    $log.val($log.val() + line);

    // scroll to the bottom of the log
    $log.scrollTop($log[0].scrollHeight);

    // the event provides stats on the number of completed items
    $totalProgress.text(e.completedCount + ' / ' + e.totalCount);
});

// start downloading images for tags in prioritized order
loader.start(['menu', 'game']);

See it in action

Menu
0 / 50
Game
0 / 50
Total
0 / 100

SAMPLE 4

Load a Set of Sound Effects

For this sample, we'll load 5 audio clips. Once all of the sounds are ready, we'll show a set of images that can be clicked to play each sound.

Code

The PxLoaderSound plugin relies on SoundManager2 to load and play audio files. First, add the soundManager2 and PxLoaderSound script references:

<script type="text/javascript" src="soundManager2.js"></script>
<script type="text/javascript" src="PxLoaderSound.js"></script>

Next, you need to initialize the soundManager. Here are the settings we use:


// initialize the sound manager
soundManager.url = 'soundManager2/';
soundManager.flashVersion = 9;
soundManager.useHighPerformance = true; // reduces delays

// reduce the default 1 sec delay to 500 ms
soundManager.flashLoadTimeout = 500;

// mp3 is required by default, but we don't want any requirements
soundManager.audioFormats.mp3.required = false;

// flash may timeout if not installed or when flashblock is installed
soundManager.ontimeout(function(status) {
    // no flash, go with HTML5 audio
    soundManager.useHTML5Audio = true;
    soundManager.preferFlash = false;
    soundManager.reboot();
});

soundManager.onready(function() {
    // ok to show the button to run the sound sample
    $('#sample4-run').show();
});

Finally, here is the code to load and play each audio clip:


var soundNames = ['cow', 'pig', 'tractor', 'dino', 'r2d2'],
    loader = new PxLoader(),
    i, len, url;

// queue each sound for loading
for(i=0, len = soundNames.length; i < len; i++) {

    // see if the browser can play m4a
    url = 'audio/' + soundNames[i] + '.m4a';
    if (!soundManager.canPlayURL(url)) {

        // ok, what about ogg?
        url = 'audio/' + soundNames[i] + '.ogg';
        if (!soundManager.canPlayURL(url)) {
            continue; // can't be played
        }
    }

    // queue the sound using the name as the SM2 id
    loader.addSound(soundNames[i], url);
}

// listen to load events
loader.addProgressListener(function(e) {

    // show the icon once a sound has loaded
    var soundId = e.resource.sound.sID,
        $icon = $('#' + soundId).addClass('ready');

    // play the sound when the icon is clicked
    $icon.click(function() {

        // highlight the icon while playing
        $icon.addClass('playing');

        soundManager.play(soundId, {
            onfinish: function() {
                $icon.removeClass('playing');
            }
        });
    });
});

loader.start();

See it in action

Sounds (CC-BY): cow, pig, tractor, dino, droid

DOCUMENTATION

Documentation for the Core API

new PxLoader(settings)

You can override the default settings when creating a new PxLoader instance.

Code


// create a loader using the default settings
var loader = new PxLoader();

// create a loader with custom settings (shown with default values)
var loader = new PxLoader({
        // how frequently we poll resources for progress
        statusInterval: 5000, // every 5 seconds by default

        // delay before logging since last progress change
        loggingDelay: 20 * 1000, // log stragglers after 20 secs

        // stop waiting if no progress has been made in the moving time window
        noProgressTimeout: Infinity // do not stop waiting by default
    });

add(resource)

Add a resource to the the loading queue. This generic method allows PxLoader to handle any type of resource that has a PxLoader plugin.

Code


// add an image with a url
loader.add(new PxLoaderImage('/image1.png'));

// add an image with a single tag
loader.add(new PxLoaderImage('/image2.png', 'tag1'));

// image with multiple tags
loader.add(new PxLoaderImage('/image3.png', ['tag1', 'tag2']);

// image with a priority (relative to another resource with the same tag)
loader.add(new PxLoaderImage('/image4.png', ['tag1', 'tag2'], 1);

Plugins may also add a convenience method to PxLoader which makes adding a new resource a little easier and more concise.

Code


// The PxLoaderImage plugin provides addImage which returns the img element
// which can be used once the image has been downloaded
var imgElement1 = loader.addImage('/image1.png');
    imgElement2 = loader.addImage('/image2.png', 'tag1');

addProgressListener(callback, tags)

Registers a callback that will be called anytime an event occurs for a resource with the specified tag(s). If the tags parameter is omitted then progress updates will be provided for every resource.


// log the name of every resource as its loaded
loader.addProgressListener(function(e) {
    console.log(e.resource.getName());
});

// the callback function receives an event object:
var sampleEvent = {

    // the updated resource
    resource: {} // the resource parameter provided to loader.add()

    // status for the updated resource
    loaded: true,
    error: false,
    timeout: false,

    // stats for all resources that match the listener's tags
    completedCount: 42,
    totalCount: 100
};

addCompletionListener(callback, tags)

Works in the same way as the progress listener API, except instead of receiving updates for each change you are notified once everything completes.

Code


// log when all resources have completed
loader.addCompletionListener(function(e) {
    console.log('Ready to go!');
});

log(showAll)

Writes a list of resources to the browser console. If showAll is true then all resources are logged, otherwise resources that have completed (loaded, error, or timeout) are omitted.

Code


// write a list of resources we are still waiting for
loader.log();

isBusy()

Returns true if the loader is still waiting for some resources to complete.

start(orderedTags)

Starts downloading all queued resources. Resources are ordered and started by tag first and then by priority. Its important to register all event listeners before calling start() because its possible for the loader to complete very quickly if resources are cached.

Code


// start loading resources according to resource priority alone
loader.start();

// start resources by tag first and then by priority
loader.start(['tag1', 'tag2']);

Plugins

Images

PxLoaderImage loads images using JavaScript <image> elements. We listen to load and readystate change events. Some browsers won't send events for cached images so we also check the iscomplete status during the periodic poll initiated by the loader. Errors are reported to the loader, and details can be retrieved from the img element's errors property.

SoundManager 2

PxLoaderSound loads audio using the SoundManager2 library. We're fans of this great little library because it supports HTML5 audio when available and falls back to flash when necessary.

Videos

Samples showing how to use this plugin are coming soon.

Plugin API

Writing a plugin so PxLoader can track another resource type is pretty easy. There are just a few functions that a resource should implement to communicate with the loader. Here is a skeleton that you can use to start.

Code


function PxLoaderResource(url, tags, priority) {
    var self = this;
        loader = null;

    // used by the loader to categorize and prioritize
    this.tags = tags;
    this.priority = priority;

    // called by PxLoader to trigger download
    this.start = function(pxLoader) {
        // we need the loader ref so we can notify upon completion
        loader = pxLoader;

        // set up event handlers so we send the loader progress updates

        // there are 3 possible events we can tell the loader about:
        // loader.onLoad(self);    // the resource loaded
        // loader.onError(self);   // an error occured
        // loader.onTimeout(self); // timeout while waiting

        // start downloading
    };

    // called by PxLoader to check status of image (fallback in case
    // the event listeners are not triggered).
    this.checkStatus = function() {
        // report any status changes to the loader
        // no need to do anything if nothing has changed
    };

    // called by PxLoader when it is no longer waiting
    this.onTimeout = function() {
        // must report a status to the loader: load, error, or timeout
    };

    // returns a name for the resource that can be used in logging
    this.getName = function() {
        return url;
    }
}

Get Involved

We'd love to hear what you think about PxLoader. It's a work in progress and we'd welcome any suggestions or code contributions. You can fork us on Github.

There are also lots of opportunities to extend PxLoader to handle additional resource types. Video (contributed!) and JSON are a few we'd love to see. Please remember that not just some, but "all your resource are belong to us".

Happy loading!