先日、
GIFをparseするjsなライブラリを作ったりしましたが、
parseするのにそれなりな時間が掛かったりするので、
progress表示とかがうまくやれない感じ。
そんなわけで、
web worker を使って別スレッドで動かすことで、
非同期でやれるように作り直してみることしました。
ところで、
web worker を使うには、
var worker = new Worker('worker.js');
とかやって、
別なソースファイルからworkerを作るのが普通だと思います。
workerを使うのは久しぶりだったので、
あらためて使い方なぞをネットで調べていたら、
ここやここに書いてあるような
inline worker という手法があることを知りました。
実質的に以下のようなことが出来てしまいます。
var worker = new Worker(function() {
︙
});
個人的にこんな発想はありませんでした(^_^;)
なるほど分かりやすい感じになりますね。
ソースファイルを分けなくても行けるのが便利です!
どうやって実現しているのかと思ったら、
関数のコード ⇒ 文字列化 ⇒ blob ⇒ ObjectURL
とかやってました。
なるほど納得!
うまいこと考えましたね。
ソースコードのイメージを作って、
ブラウザ内のメモリに置いたのをURLで参照という感じかな。
なお、
上記のページでは、
“text/javascript”なMIMEでblobを作っていますが、
“application/javascript”の方が新しい指定なようです。
どっちでも動くと思いますが。
同じソースファイルに書けるとは言っても、
workerとして渡す関数は別スレッドで動くので、
必要なコードが完結するようにしないといけません。
主スレッド側に書いた関数とか呼べないので要注意です。
1つのソースファイルに書けてしまうので、
いかにも呼べそうに見えるのが罠です(^_^;)
また、こちらによると、
inline-worker内でimportScripts()する場合は、
絶対URLで指定する必要があります。
相対パスだと例外が起きます。
inline-worker自体はblobスキームで生成されるので、
cross-domainな制約を受けるためだそうです。
相対パスな指定だとblob:
扱いになってしまうっぽい。
ちなみに、
デバッグしていて気付いたのですが、
worker側からErrorオブジェクを送信したら、
“Uncaught DOMException: Failed to execute ‘postMessage’ on ‘DedicatedWorkerGlobalScope’: An object could not be cloned.”
こんな感じのエラーが起きました。
調べてみても理由がよく分かりませんでしたが、
Errorオブジェクトにはstackトレースが含まれてるためだと思います。
スレッド固有な情報を別なスレッドで処理したらマズイことになりそうだし。
とか思ったんですが、
{message: error.message, stack:error.stack}
こんな感じのオブジェクトを作って返すと例外は起きませんでした。
ということで例外起きる理由はやっぱよく分からない・・・。
しょうがないので文字列化して送信しました(^_^;)
ということで、
inline worker な手法を取り入れて、
ライブラリを作り直しました。
ここで書いた具体例のコードも以下のように変わります。
function animateGIF(arrayBuffer,canvas) {
var gif,context,frames,idx,delay,lastTime;
gif = new Gif();
gif.onparse = function() {
start();
};
gif.onerror = function(e) {
alert(e);
};
gif.onprogress = function(e) {
console.log(((100 * e.loaded / e.total)|0) + '%');
};
gif.parse(arrayBuffer);
function start() {
canvas.width = gif.header.width;
canvas.height = gif.header.height;
context = canvas.getContext('2d');
frames = gif.createFrameImages(context,1);
idx = 0;
delay = 0;
draw(); // first draw
if (frames.length > 1) {
setTimeout(function() {
lastTime = performance.now();
tick(lastTime);
},0);
}
}
function draw() {
var frame;
frame = frames[idx];
delay += frame.delay * 10; // 1/100sec to millisec
if (idx === 0) {
context.clearRect(0,0,context.canvas.width,context.canvas.height);
}
context.drawImage(frame.image,frame.left,frame.top);
}
function tick(time) {
var delta;
delta = time - lastTime;
lastTime = time;
delay -= delta;
if (delay <= 0) {
idx = (idx+1) % frames.length;
draw();
}
requestAnimationFrame(tick);
}
}
拙作のライブラリを使って、
作ってみたツールはこちらから試せます。
ライブラリのダウンロードも行えます。