How do you deal with html5's canvas image load

2019-01-27 01:08发布

问题:

I've been learning about html5's canvas. Because images can take a while to load, it seems the appropriate technique is to use onload to wait for the image to load before attempting to draw it. So:

<canvas id="fig" width="400" height="400"></canvas>

var fig = document.getElementById('fig1');
var ctx = fig.getContext('2d');

var img = new Image();
img.onload = function() { ctx.drawImage(img, 300, 100); };
img.src = 'data:image/gif;base64,R0lGODlhCwALAIAAAAAA3pn/ZiH5BAEAAAEALAAAAAALAAsAAAIUhA+hkcuO4lmNVindo7qyrIXiGBYAOw==';

However, it is likely that code following this gets executed before the onload function can do the drawImage(), possibly causing undesired behavior:

ctx.translate(0,400); ctx.scale(1,-1);   /* switch to lower-left as origin */
/* if onload happens after here, it uses new coordinate system! */
ctx.beginPath();
ctx.moveTo(10, 20);
ctx.lineTo(290, 30);
ctx.stroke();

Surely there is some sensible way of dealing with this (and doing everything in the onload function seems non-sensible).

========================== EDIT BELOW ========================

Here is a change to my code using a single promise to illustrate the idea as simply as I can.

var img = new Image();
var promise = new Promise(                         // make a promise
        function(resolve, reject) {
                img.onload = function() {
                        ctx.drawImage(img, 300, 100);   
                        resolve();     // keep the promise -- lets the "then" proceed
                };      
                img.src = 'data:image/gif;base64,R0lGODlhCwALAIAAAAAA3pn/ZiH5BAEAAAEALAAAAAALAAsAAAIUhA+hkcuO4lmNVindo7qyrIXiGBYAOw==';
        }
);

// .then() waits for the promise to be resolved (see also .catch for rejection)
promise.then( function() {
        ctx.translate(0,400); ctx.scale(1,-1);
        ctx.beginPath();
        ctx.moveTo(10, 20);
        ctx.lineTo(290, 30);
        ctx.stroke();
});

回答1:

Pre-load your images before working with the canvas. Put all your image urls into an array, loop through the array creating new images, and when they are all loaded call a function that will start your canvas work.

Following snippet uses the native JS Promises, but if supporting older browsers that do not have native Promise you can use Q or jQuery libraries in a similar fashion

var images = ['imageurl.jpg','imageurl2.jpg','imageurl3.jpg'];
var loadedImages = {};
var promiseArray = images.map(function(imgurl){
   var prom = new Promise(function(resolve,reject){
       var img = new Image();
       img.onload = function(){
           loadedImages[imgurl] = img;
           resolve();
       };
       img.src = imgurl;
   });
   return prom;
});

Promise.all(promiseArray).then(imagesLoaded);

function imagesLoaded(){
   //start canvas work.

   //when needing to draw image, access the loaded image in loadedImages
   ctx.drawImage(loadedImages['imageurl.jpg'], 300, 100);
}


回答2:

An alternative is to add a semaphore to the images. I do this for some apps where the image loading is an ongoing process and load times can be inconsistent and subject to failure. You can not stop the app because an image is missing, or taking its time to load so you just check if the image is ready and render it when it is or use a placeholder which can be rendered on the client and does not need to be loaded.