1

I have a canvas and 100 tiny images that I want to add to my canvas in a grid-like formation.

I have already achieved this, but the images are being draw on the canvas one by one and sometimes the image at the 40th position might be drawn before the image at the 10th position.

It takes a couple of seconds and then I get my desired result, but I was wondering how I could show images only after they're loaded?

I know there are other questions with a similar problem but I have read all of them and tried their solutions but have not succeeded yet.

I have this piece of code that draws the images one by one on the canvas at the right position using nested for loops:

for(...) {
  for(..) {
    add_image(source, row, column, width, height);
  }
}

Now add_image actually creates an image object and draws it onload

function add_image(...) {
  let image = new Image();
  image.src = source;

  image.onload = function() {
    context.drawImage(img, row, col, width, height);
  }
}

In this exact situation, what is the best way to load all the images first and the "draw" them? I tried creating an array and populating it with all the image objects, then when all the images were loaded I would run the for loop to draw the images, but it wouldn't work and I didn't see a console error so I was left clueless.

Thank you in advance!

Toby
  • 13
  • 2
  • 1
    Do you want to wait until they are all loaded before displaying any of them? The other approach would be as described in this answer, which displays images in the correct sequence as soon as they are available http://stackoverflow.com/questions/39506073/how-to-make-sure-images-load-sequentially/39526950#39526950 – John M Oct 22 '16 at 11:02
  • 1
    This has been asked a **million** times on the internet. have you tried googling `javascript preloading images` or `javascript wait until all images are loaded` and the likes? – vsync Oct 22 '16 at 11:40

2 Answers2

1

Or use Promise.all...

function preloadImage(source) {
  return new Promise((resolve, reject) => {
    const image = new Image();

    image.addEventListener('load', resolve.bind(null, image));
    image.src = source;
  });
}

const images = []; // Array of image sources

Promise.all(images.map(preloadImage)).then((imgs) => {
  // Draw them :)
});

Here's a demo on jsfiddle.

jjenzz
  • 1,519
  • 1
  • 14
  • 19
0

Add the images to an array and count each image as you do. In the image onload event decrease the counter. When the count is zero all the images have been loaded and can be drawn.

Example:

var images = [];
var loading = 0;
function add_image(url,xpos,ypos) {
  var image = new Image();
  image.src = url;
  images.push({
     image : image,
     xpos : xpos,
     ypos : ypos,
  });
  loading += 1;
  image.onload = function() {
      loading -= 1;
      if(loading === 0){
         drawImages(); // call function to draw all the images.
      }
  }
}
function drawImages(){
   for(var i = 0; i < images.length; i ++){
       // draw the image
       ctx.drawImage(images[i].image,images[i].xpos,images[i].ypos);
   }
}
Blindman67
  • 51,134
  • 11
  • 73
  • 136
  • I would recommend to use two counters a toLoad (images.length) and a loaded one. In your implementation, if the first image is cached, the onload event may fire before the next loop as occured.(sounds like a bug but it happens every so and then) – Kaiido Oct 23 '16 at 00:59
  • @Kaiido An event can not interrupt the current execution, this is a basic necessity of the language.. As long as all the images are added within the same call using one count is perfectly safe. This example "First" will always be before "second", if a bug made this untrue JS would be unusable. Image is just a 1 pixel black gif `var i=new Image();i.onload=function(){console.log("second");};i.src="data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=";var n=performance.now(); while(performance.now() < n +3000);console.log("First");` – Blindman67 Oct 23 '16 at 02:32
  • not true : https://jsfiddle.net/d4e5wfgv/ and there is a long list of bugged behavior about it here on SO: e.g http://stackoverflow.com/questions/1038327/image-onload-not-firing-twice-in-ie7 – Kaiido Oct 23 '16 at 03:05
  • @Kaiido Dispatchevent is a call to a function, not an event. That is not a bug and the current execution remains blocked, your example is just bad code and the same as `console.log("second");console.log("first")` Nor do I accept dredging up a 10 year old bug covered monster (IE7). Don't you think the bigger problem is the drawImage call when there is no canvas support. The mandatory vetting out browsers that do not support canvas makes it a mute point – Blindman67 Oct 23 '16 at 11:59
  • Is this bad faith? Please have a look at [mdn](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent). What happens here is that CustomEvent are blocking events. Actually, nowhere in the specs is written that events handler should be delayed, even if major browsers seems to agree on waiting till the end of the execution. And even if the question I linked to is about an old browser, there are still reports of such behavior on latests versions too. – Kaiido Oct 23 '16 at 13:14
  • @Kaiido The spec?? We are talking about Javascript. JS execution is single threaded and non interruptible (blocking). When execution is started by a call placed on the call stack nothing else but calls from within the current execution can run. If what you say is true then please do use despatch event to break out of the while loop `window.flag = true;while(window.myFlag);` , all you need to do is set global `window.flag=false` by interrupting execution. Use any event, it just will not happen within the JS context. As for buggy "there are still reports" anecdotal evidence, please demonstrate – Blindman67 Oct 23 '16 at 13:42