HTML5 Canvas: Get Event when drawing is finished

38,279

Solution 1

Like almost all Javascript functions, drawImage is synchronous, i.e. it'll only return once it has actually done what it's supposed to do.

That said, what it's supposed to do, like most other DOM calls, is queue-up lists of things to be repainted next time the browser gets into the event loop.

There's no event you can specifically register to tell you when that is, since by the time any such event handler could be called, the repaint would have already happened.

Solution 2

Jef Claes explains it pretty well on his website:

Browsers load images asynchronously while scripts are already being interpreted and executed. If the image isn't fully loaded the canvas fails to render it.

Luckily this isn't hard to resolve. We just have to wait to start drawing until we receive a callback from the image, notifying loading has completed.

<script type="text/javascript">        
window.addEventListener("load", draw, true);

function draw(){                                    
    var img = new Image();
    img.src = "http://3.bp.blogspot.com/_0sKGHtXHSes/TPt5KD-xQDI/AAAAAAAAA0s/udx3iWAzUeo/s1600/aspnethomepageplusdevtools.PNG";                
    img.onload = function(){
        var canvas = document.getElementById('canvas');
        var context = canvas.getContext('2d');    

        context.drawImage(img, 0, 0);        
    };            
}                    

Solution 3

You already have an event when the image loads, and you do one thing (draw). Why not do another and call the function that will do whatever it is you want done after drawImage? Literally just:

myImg.onload = function() {
    myContext.drawImage(containerImg, 0, 0, 300, 300);
    notify(); // guaranteed to be called after drawImage
};
Share:
38,279

Related videos on Youtube

elHornair
Author by

elHornair

Updated on October 20, 2021

Comments

  • elHornair
    elHornair over 2 years

    I'm drawing an image to a canvas element. I then have code that depends on this process to be finished. My code looks like this:

    var myContext = myCanvasElement.getContext('2d'),
        myImg = new Image();
    
    myImg.onload = function() {
        myContext.drawImage(containerImg, 0, 0, 300, 300);
    };
    
    myImg.src = "someImage.png";
    

    So now, I would like to be notified when drawImage is done. I checked the spec but I couldn't find either an event or the possibility to pass a callback function. So far I just set a timeout, but this obviously is not very sustainable. How do you solve this problem?

  • Alnitak
    Alnitak almost 12 years
    @Loktar tricky to explain more fully without diluting the answer, I think... :(
  • Loktar
    Loktar almost 12 years
    no you're right I reread it and perfectly understood it, I just imagined a new dev saying "synchronous" huh? That's why I ended up deleting my comment.
  • Alnitak
    Alnitak almost 12 years
    @Loktar hopefully my edit adds just enough to make it obvious :)
  • BBog
    BBog about 11 years
    It doesn't seem to be called after drawImage... the test I just made showed me the drawImage method is not done synchronously
  • BBog
    BBog about 11 years
    Do you have a link to some documentation that says the function is synchronous? Unfortunately (for me), my tests just showed me it's not and I'm a bit confused
  • Alnitak
    Alnitak about 11 years
    @BBog all JS functions are synchronous, unless documented otherwise. If you think .drawImage is async I'd like to see a jsfiddle that demonstrates it.
  • BBog
    BBog about 11 years
    @Alnitak I made a fiddle here: jsfiddle.net/FxrSa The alert is shown before the image. However, the alert is shown before the whole jsfiddle site is rendered so it just might be the alert test causing my problem
  • Alnitak
    Alnitak about 11 years
    @BBog curious - I see what you mean. The W3C spec for canvas doesn't say anything about this behaviour. I'm still looking into it...
  • Alnitak
    Alnitak about 6 years
    @BBog right, that'll be because the drawImage repaint doesn't happen until the event loop regains control, but alert() blocks the browser before that point. In general use drawImage should behave synchronously.
  • Alnitak
    Alnitak almost 6 years
    @MatthewCornelisse can you throw up a demo that shows this?
  • Alnitak
    Alnitak almost 6 years
    this is completely irrelevant to the question asked - the OP was already using an onload handler.
  • Kaiido
    Kaiido over 2 years
    Comparing this to a microtask is very confusing. It doesn't match anything of a microtask, it just happens after, but you can very well schedule a task to fire in between an rAF callback and the next rendering, for instance ResiveObserver callbacks will fire in this intertime. Also, that rendering task is actually itself one "frame" before the monitor's rendering happens, in this rendering phase the browser will ask the OS compositor to update the monitor's pixel, which will happen only one frame later.
  • Kaiido
    Kaiido over 2 years
    However some configurations have access to a desynchronized option for their 2D context, which will allow their browser to use a shortcut only for that canvas, but this is only available in a few configs (win + chrome).
  • trusktr
    trusktr over 2 years
    @Kaiido Yeah, maybe "microtask" is not exactly the best term. In a sense the resize observer and paint tasks are like microtasks that happen to always run after all user-defined microtasks including any user-defined microtasks chained from user-defined microtasks, as if the resize and paint tasks are always pushed to the end of the queue, or as if user microtasks are always inserted before them. They are definitely not like macrotasks from the perspective of an animation frame task.
  • trusktr
    trusktr over 2 years
    Does a postMessage() event handler run at a different time than a promise.resolve().then() callback? Are you saying here that Promise microtasks happen before paint and postMessage (micro?)tasks happen after paint; that they are not on the same schedule?
  • trusktr
    trusktr over 2 years
    Oh wow, I didn't know queueMicrotask was a thing these days. Where have I been!
  • Kaiido
    Kaiido over 2 years
    @trusktr postMessage() will queue a task. Promise.resolve().then() will queue a microtask. They are completely different. The postMessage task will get executed at the next event loop's iteration as part of its first step. The microtask will get executed as soon as the JS stack is empty, that is the microtask queue is emptied many times per event-loop's iteration. Also, a microtask queued from an other microtask will get executed from the same microtask-checkpoint, no way to get out of it. So you can't schedule anything using microtasks, f = ()=>Promise.then(f);f() is while(1)
  • trusktr
    trusktr over 2 years
    Ah. Thanks. The conversation at github.com/whatwg/html/issues/4905 clears that up by pointing to the relevant parts of spec (postMessage queues a macrotask that browsers tend to prioritize and run sooner than setTimeout(0)).
  • trusktr
    trusktr over 2 years
    @Kaiido I've updated the answer to call it microtask-like, and explain that the paint microtask-like always happens after "user-created microtasks". I know they are just "tasks" in spec language, but that doesn't differentiate them from setTimeout tasks for example (apart from more complicated wording found in the spec that is difficult to understand).