1

I have some code that renders specific DOM elements to canvas, sort of like taking a screenshot. (It's custom code built for a very particular DOM structure as part of a graphics editing game, not a general library like rasterHTML.js)

The code flow is pretty procedural:

  1. get some DOM elements of class A and draw them to canvas
  2. get some DOM elements of class B and draw them to canvas

The trouble is that step 1 is very intensive compared to step 2, and doesn't finish drawing before step 2, screwing up the layers (in reality I have several canvases doing several things at once, and a canvas is unfortunately resized before all the drawing is completed). I've tried to replicate this in this fiddle: https://jsfiddle.net/1hucuLg9/

In pseudocode:

context.drawComplexSVG(); // slow
context.drawSimpleImage(); //fast
//canvas now has an SVG drawn on top of an image, not underneath.

I've seen a lot of setTimeout examples in an attempt to get one function to execute after another, but to me this seems to be a bit of a hack ... ideally I don't want to delay execution, just execute everything in strict order. I've also seen the idea of postMessage floated to achieve this but I've no idea how you pass messages to yourself. What's the correct way to ensure a function/line is fully executed (or in my case, the canvas is fully updated - is it the same thing?) before proceeding?

Escher
  • 5,418
  • 12
  • 54
  • 101

2 Answers2

1

"getting some DOM elements" should be synchronous and do not require any complex code to handle sequencing draw operations.

The problem you are facing in your fiddle is that you are dynamically loading some images to draw - and for those, you need to wait, which makes the operation asynchronous.

Promises are here for your rescue, but you'll have to use them correctly. Just calling resolve right away like you did in your own answer will ensure some asynchrony, but that's not less fragile than a setTimeout approach. Instead, you should always create the promise at the heart of the asynchrony, in your case the image loading:

function loadImage(src) {
    return new Promise(resolve, reject) {
        var img = new Image();
        img.onload = function(){ resolve(img); };
        img.onerror = reject;
        img.src = src;
    });
}

so that you can use it in your canvas drawing code:

function drawSwatches(currentSwatch) {
    var data = …;
    var url = 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent(data);

    return loadImage(url).then(function(img) {
        ctx.drawImage(img, 0, 0, vw, vh);
    });
}

and then chain these properly:

var swatches = Array.from(document.getElementsByClassName("swatch"));
swatches.reduce(function(promise, swatch) {
    return promise.then(function() {
        return drawSwatches(swatch);
    });
}, Promise.resolve()).then(function() {
    var otherObjects =  document.getElementsByClassName("otherObjects");
    for (var i=0; i<otherObjects.length; i++) {
        drawOtherObjects(otherObjects[i], 0, 0, 100, 100);
    }
});
Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Thanks for a great template on how to correctly do asynchronous image loading; it should be useful for others like me who could only find ajax examples that fetch JSON and couldn't make the mental leap to understanding the significance in relation to the `onload` part of image loading. – Escher Dec 17 '15 at 15:35
0

Well, it seems that this will get the job done:

var promise = new Promise(function(resolve){
  context.drawComplexSVG();
  resolve();
}
promise.then(function(){
  context.drawSimpleImage();
}
Escher
  • 5,418
  • 12
  • 54
  • 101