« Eclipseで同じファイルを別々に表示する | メイン | 超強引に画像データを同期して読み込む方法 »

Imageのsrcプロパティは非同期で読み込まれる

次のようなプログラムを書いていてまたハマった。
var canvas=document.createElement("canvas");
canvas.width=160;
canvas.height=120;
var ctx=canvas.getContext("2d");
var img=new Image();
img.src="data:image/png;base64,(略)";
ctx.drawImage(img,0,0);
var imageElem=document.createElement("img");
imageElem.src=canvas.toDataURL("image/png");
document.body.appendChild(imageElem);
読み込んだ画像をcanvasに描画しているのだけど、「img.src="..."」の部分で読み込む画像がJavaScriptの実行と非同期で実行されるようで、drawImageが実行された時に画像が読み込み済でないとそこで実行が終了してしまう。画像が大きかったりキャッシュされていない場合に特に顕著だ。以前、同じような状況の時には次のような方法で回避したことがある。
var f=function(img){
    return function(){
        var canvas=document.createElement("canvas");
        canvas.width=160;
        canvas.height=120;
        var ctx=canvas.getContext("2d");
        ctx.drawImage(img,0,0);
        img.parentNode.removeChild(img);
        var imageElem=document.createElement("img");
        imageElem.src=canvas.toDataURL("image/png");
        document.body.appendChild(imageElem);
    }
}
var img=document.createElement("img");
img.style.display="none";
document.body.appendChild(img);
img.addEventListener("load",f(img),false);
img.src="data:image/png;base64,(略)";
読み込みたい画像が多い場合は、画像数をカウントする事で対応可能だ。
var f=function(imgs){
    var cnt=imgs.length;
    return function(){
        if(--cnt!=0){return;}
        //(略)
    }
}
//(略)
var imgs=[];
imgs.push(img1);
imgs.push(img2);
imgs.push(img3);
var handler=f(imgs);
for(var i=0;i<imgs.length;i++){
    imgs[i].addEventListener("load",handler,false);
}
//(略)
画像を読み込むタイミングが一カ所の場合はまだ良い。しかし画像を読み込むタイミングで処理が分断されてしまうので、複数箇所で画像読み込みを行ないたい場合は非常に複雑になってしまう。なんとかうまい方法はないものか...。
2009/12/24 追記
はてなブックマークのコメントでonloadで良いのではないかとツッコミがありました。調べてみるとMDCにも載ってますね。知らなかった。
普段、「onXxx」という書き方は避けて「addEventListener」を使っているのと、DOMに追加しないとイベントは発火しないと思っていたので上記のように書いてました。考えてみれば、一時的に作成されたImageオブジェクトなので、onloadを使っても問題にならないんですね。
で、実験。
var f=function(){
    var canvas=document.createElement("canvas");
    canvas.width=160;
    canvas.height=120;
    var ctx=canvas.getContext("2d");
    ctx.drawImage(this,0,0);
    var imageElem=document.createElement("img");
    imageElem.src=canvas.toDataURL("image/png");
    document.body.appendChild(imageElem);
}
var img=new Image();
img.onload=f;
img.src="data:image/png;base64,(略)";
なるほど。少し簡素化されました。
addEventListenerでもやってみます。
var f=function(){
    var canvas=document.createElement("canvas");
    canvas.width=160;
    canvas.height=120;
    var ctx=canvas.getContext("2d");
    ctx.drawImage(this,0,0);
    var imageElem=document.createElement("img");
    imageElem.src=canvas.toDataURL("image/png");
    document.body.appendChild(imageElem);
}
var img=new Image();
img.addEventListener("load",f,false);
img.src="data:image/png;base64,(略)";
こちらも動きますね。
ただ、このエントリーで言いたかったのは、間にイベントを挟む事で処理が分断されてしまうのをなんとかしたいという事だったので、これでも不十分なんですね。
実際のコードでは、次のような処理をやろうとしていました。
var makeDataScheme=function(){
    var canvas=document.createElement("canvas");
    canvas.width=160;
    canvas.height=120;
    var ctx=canvas.getContext("2d");
    ctx.globalCompositeOperation="lighter";
    var img1=new Image();
    img1.src="data:image/png;base64,(略)";
    ctx.drawImage(img1,0,0);
    var img2=new Image();
    img2.src="data:image/png;base64,(略)";
    ctx.drawImage(img2,0,0);
    return canvas.toDataURL("image/png");
}
functionで画像処理の結果を返したかったので、処理をイベントで分断したくはなかったのです。説明不足でした。
2009/12/25 追記
続き書きました。

コメントを投稿

(いままで、ここでコメントしたことがないときは、コメントを表示する前にこのブログのオーナーの承認が必要になることがあります。承認されるまではコメントは表示されません。そのときはしばらく待ってください。)

Google

タグ クラウド