HTML5 + Javascript + canvas でダブルバッファリングをする (Double Buffering)

とても基本的かつ必要な技だと思うにの、意外にあんまり情報がなかったので、メモ的に作ってみた。実際問題として、動くものが少ないときはダブルバッファリングしなくてもそこそこ綺麗に動くんだけど、動かすものが増えてくるとちらちらしてきてダブルバッファリングしたくなる。まあ、多いといっても 5000 個くらい動かす程度なら全然平気ぽい。サンプルでは 10000 個にしてあるけど、環境によってはこれでも全然平気かも。

↑矩形をランダムな方向に10000個ほど動かしまくるサンプルのキャプチャ画像。静止画で見るとただのノイズ画面にしか見えないね・・・。

ふたつの canvas をフリップする

このへんに書いてあった方法そのまま。canvas を二つ作っておいて、片方を hidden、片方を visible に設定する、というのを繰り返す方法。

HTML ファイルは、以下のような感じで作っておく。位置とサイズが同じふたつの canvas 要素を配置しておく。

<!DOCTYPE html> 
<html> 
<head> 
<style>
canvas { 
border: 2px solid #000;
position:absolute;
top:0;
left:0; 
visibility: hidden; 
}
</style>
<meta charset="utf-8"/> 
<script type="text/javascript" src="s1.js"></script> 
</head>

<body>
<canvas id="canvas1" width="800" height="640"> </canvas> 
<canvas id="canvas2" width="800" height="640"> </canvas> 
</body>

</html>

で、Javascript 側で描画するときにこれを切りかえる。

    var ga_canvas = {
	0: document.getElementById("canvas1"),
	1: document.getElementById("canvas2")
    };
    var ga_flip = 0;

初期化時にこんなかんじで canvas 要素を取得しておく。

    ga_canvas[1-ga_flip].style.visibility='hidden';
    ga_canvas[ga_flip].style.visibility='visible';

    ga_flip = 1 - ga_flip;

    ga_ctx = ga_canvas[ga_flip].getContext('2d');

描画するときは、このようにして canvas の hidden/visible を交互に切りかえつつ、context の取得さきも変えるようにする。とりあえずこれだけで行けてるぽい。

getImageData/putImageData を使う

canvas をふたつ用意するのは同じなんだけど、hidden/visible の切り替えはしない。常に hidden に設定されている側の canvas に書きこんで、最後に hidden な canvas から visible な canvas に getImageData と putImageData をつかってごっそり中身のピクセルをコピーする。

    imagedata = ga_ctx.getImageData(0,0,ga_w,ga_h);

    ga_ctx = ga_canvas[0].getContext('2d');
    ga_ctx.putImageData(imagedata, 0,0 );

こんなかんじ。この例では ga_canvas[0] を常時 visible にしておき、1 を常時 hidden にしておく。

どっちが効率がいいのかは、ブラウザとかの動作次第かもしれない。とりあえず、私のところで試した限りでは、どのブラウザでも速度などの差異は見られなかった。