B1NARY
B1NARY

Reputation: 163

Drawing multiple images on an HTML5 canvas doesn't work sometimes

I'm trying to draw multiple images inside a canvas. When I load a page it works (every image draws in the correct location), the issue I'm having is that when I reload the page only 1 image will draw (in it's correct location).

The code is simply a for loop that goes through an array of image URLs and determines where that image is supposed to be located.

When I reload the page and only 1 image is drawn, that one image happens to be whatever image is located in the header of the page. If I go to another page that runs this same code but has a different header image it will display all the images correctly, but when I reload the page only the image that also happens to be in the header displays correctly. If I go back to the first page all the images will display correctly, and if I reload only the image that also happens to be in the header will display.

// Get the image
var image = new Image();
var image_load = function( img, x_pos, y_pos, width, height ) {

    // Draw the image
    context.drawImage( img, x_pos, y_pos, width, height );
};
image.src = image_url[index];
image.onload = image_load( image, x_position, y_position, img_size, img_size );

I've made sure that the image urls are all properly set and that the positions & sizes are all correct. The other thing I've noticed is that when I break through the code the image object always has the outerHTML and src attributes set correctly, but the currentSrc is only set when the image displays correctly, and is set to "" whenever it doesn't.

I've been playing with this for hours and no online example I've found has worked for me. Any help would be much appreciated.

Upvotes: 3

Views: 775

Answers (1)

iMoses
iMoses

Reputation: 4348

It seems that you are executing the image_load function instead of assigning it to the load event listener. You are suppose to pass a function handler to be executed when the event occurs, in your case what you are passing is the result of image_load, which by your code example is undefined.

Try using the following line of code instead of yours:

image.onload = function(){ image_load( image, x_position, y_position, img_size, img_size ); };

Notice that we are passing a function which when executed will call image_load with the desired arguments instead of executing it on the spot.

Edit: If you use the same variables with all handlers then consider the fact the it takes time for the image for load, in that time you probably overwritten previously defined arguments with the new image details, probably resulting with all of the images receiving the values from the last call which defined these variables. There are two solutions I would recommend in this situation.

  1. Wrap your code so that every image will defined its own variables.

    function createImage(image_url, x_position, y_position, width, height) {
        var image = new Image();
        image.onload = function() {
            var context;
            // ... Some code ...
            // Draw the image
            context.drawImage( image, x_position, y_position, width, height );
        };
        image.src = image_url;
        return image;
    }
    

Notice that we are passing the arguments through the parent function createImage which creates a new scope. Each function execution will have its own separated scope and values won't mix.

  1. You can bind the values to the event handler this way.

    var image = new Image();
    var image_load = function( img, x_pos, y_pos, width, height ) {
    
        // Draw the image
        context.drawImage( img, x_pos, y_pos, width, height );
    };
    image.src = image_url[index];
    image.onload = image_load.bind( null, image, x_position, y_position, img_size, img_size );
    

Bind will return a version of the function that automatically sets it's context to null (this within the function will be null) and the first few arguments to: image, x_position, y_position, img_size, img_size. Since we're binding the values to the function, even if the variables will change values it won't matter to our handler.

If it was up to me I'd probably separate the two functions into something like this:

function loadImage(x_position, y_position, width, height) {
    var context;
    // ... Some code ...
    // Draw the image
    context.drawImage(this, x_position, y_position, width, height);
}

function createImage(image_url, x_position, y_position, width, height) {
    var image = new Image();
    image.onload = loadImage.bind(image, x_position, y_position, width, height);
    image.src = image_url;
    return image;
}

Using loadImage image as the context (this).

P.S. I switched between image.onload and image.src = ..., it's a good idea to set the event handlers before setting the source and beginning the image download.

Upvotes: 2

Related Questions