Lee Brindley
Lee Brindley

Reputation: 6482

Canvas/JS storing vector/bitmap objects

In my canvas project I have two types of sprite object, those that are drawn with vector graphics and those that have a bitmap image drawn to the canvas.

draw function(context){
 ...
 context.lineTo(some place)
 context.lineTo(some other place)
 etc.
}

The other type of sprite is a bitmap image, at the minute they're placeholder assets so are small enough to not need an onload event handler, but the real assets will be larger, so will need one.

bitmap loadImage function(){

   this.image = new Image(); 
   //Currently my images are too small to warrant an onload eventHandler as they're placeholders
   this.image.src = "some path";
} 

Currently i'm storing all of my sprites in the same container and execute the drawing with a simple loop. This works for now, as the bitmap sprites don't have an onload function.

for each sprite {

 sprite.draw(context);
 }

Once i've replaced the placeholder assets with spritesheets, they're going to need a function assigned to the onload event - this brakes my design.

Can anyone shed any light on how I could achieve storing all the sprites in the same container and calling draw through iterating through that collection?

Note: With the onload event handler added the bitmaps draw, however (obviously) i'm getting an error when draw is called on the bitmap sprite before the image is loaded.

Upvotes: 0

Views: 634

Answers (2)

user1693593
user1693593

Reputation:

I made this loader which will allow you to add all the image URLs to it and then call load() to initiate the load.

You can use this kind of loader to support progress callback so you can display the current progress to the user.

If you need cross-origin images you can add support for this by adding a flag to tell the loaded to set crossOrigin type for you for the images. The following example sets this for all images but it can be extended to support this for individual images.

Live demo here

Loader:

/// callback - function to call when finished (mandatory)
/// progress - function to call for each image loaded (optional)
/// progress contains an argument with an "event" containing properties
/// img (the image loaded), url, current (integer) and total (integer)
function imageLoader(callback, progress, error) {

    if (typeof callback !== 'function') throw 'Need a function for callback!';

    var lst = [],
        crossOrigin = false;

    this.crossOrigin = function (state) {
        if (typeof state !== 'bool') return crossOrigin;
        crossOrigin = state;
        return this;
    }
    this.add = function (url) {
        lst.push(url);
        return this;
    }

    this.load = function () {
        if (lst.length > 0) {
            startLoading();
        }
        return this;
    }

    function startLoading() {

        var i = 0,
            url,
            count = lst.length,
            images = [];

        for (; url = lst[i]; i++) {
            var img = document.createElement('img');
            images.push(img);

            img.onload = function () {
                _handler(url, this)
            };
            img.onerror = function (e) {
                _handlerError(url, e)
            };

            if (crossOrigin === true) img.crossOrigin = 'anonymous';

            img.src = url;
        }

        function _handler(url, img) {

            count--;

            if (typeof progress === 'function') progress({
                current: lst.length - count,
                total: lst.length,
                url: url,
                img: img
            });

            if (count === 0) callback({
                images: images,
                urls: lst
            });
        }

        function _handlerError(url, e) {
            if (typeof error === 'function') error({
                url: url,
                error: e
            });

            console.warn('WARNING: Could not load image:', url);
            _handler();
        }
    }

    return this;
}

Usage:

var loader = new imageLoader(done, progress);

/// methods can be chained:
loader.add(url1)
      .add(url2)
      .add(url3)

      .load();

(see demo for full example)

The handlers then can do:

function done(e) {

    for (i = 0; i < e.images.length; i++) {

        /// draw the image
        ctx.drawImage(e.images[i], i * 20, i * 20, 40, 40);
    }
}

function progress(e) {

    ///progress bar
    status.style.width = ((e.current / e.total) * 100).toFixed(0) + '%';

    /// current loaded image
    ctx.drawImage(e.img, 0, 340, 60, 60);
}

Upvotes: 2

user1508519
user1508519

Reputation:

http://jsbin.com/IMoFOz/1/edit

Note that because it's asynchronous, you need some sort of 'state machine' or indicator that the assets are done loading. Unlike a desktop game where the loading 'blocks' the rest of the execution, you can still do stuff while the images load in the background. Therefore you can probably call a function like start that will draw your images.

Like so:

function start() {
 context.drawImage(image1, 69, 50);
 context.drawImage(image2, 150, 150);
}

  var canvas = document.getElementById('myCanvas');
  var context = canvas.getContext('2d');
  var image1 = new Image();
  var image2 = new Image();

  var Image = {
    count: 0,
    Load: function(asset, url) {
      asset.src = url;
      asset.onload = function() {
        Image.count--;
        if (Image.count == 0)
        {
          console.log("Done loading everything.");
          start();
        }
      }
      Image.count++;
    }
  };

  Image.Load(image1, 'http://www.html5canvastutorials.com/demos/assets/darth-vader.jpg');
  Image.Load(image2, 'http://www.html5canvastutorials.com/demos/assets/darth-vader.jpg');

Upvotes: 0

Related Questions