Android Eclair(2.0/2.1)ブラウザでCanvasを使うときの注意点。

  • スマートフォンだとHTML5が使えるということで、意気揚々とCanvasとかバリバリ使いたくなるのですが、罠も色々あったりするのでそんなおはなしです。
  • スマートフォンでウェブアプリを作る時、どのOSのどのバージョンをサポートするかは悩ましい問題ですが、iOSは4.0以上でAndroidは2.0(2.1)以上辺りが落とし所でしょうか。
    • 今でも2.1の端末としては、初代XperiaとIS04(REGZA Phone)などがあります。
    • iOS3はどうするかは悩みどころですが、canvasのfillText辺りにバグがあったりで今回は考慮していません。。。
  • 詳細は@yukobaさんのブログにとても分かりやすく説明されていますので、自分が試した回避法について書きたいと思います。

Android Eclair(2.0/2.1)ブラウザのCanvasのdrawImage実装がバグっている問題。

  • 上記のブログに書いてある通りなのですが下記のような問題があります。
    • drawImageを使うと、「端末幅(screen.width) / 320」という計算式で自動的に拡大してくれます。。fillTextやfillRectなどはそのまま拡大されずに描画されます。
var image = new Image(); 
image.src = "http://www.google.co.jp/intl/en_com/images/srpr/logo1w.png"; 
image.onload =function(){ 
  var ctx = document.getElementById('test').getContext('2d'); 
  ctx.drawImage(image, 20, 20, 200, 100); 
  ctx.fillRect(20, 150, 200, 100); 
}; 
    • 左がAndroid 2.2で右がAndroid 2.1です。黒のfillRectで書いた四角のサイズは同じですが、後ろの画像のサイズとX,Y座標がずれいているのがわかります。

また、scaleを呼ぶと2回適用してくれます。意味がわかりませんね。
  // 拡大することで先ほどと同じ大きさに画像を表示したい
  ctx.scale(2, 2);
  ctx.drawImage(image, 20, 20, 100, 50); 
  • widthとheightを先程の半分で指定してscaleで2倍しているので、Android 2.2 と Android 2.1ともに先ほどと同じサイズで表示されるはずですが、scaleが2回適用されるため2.1の方がとんでもなく大きくなっています。

回避方法

  • これもブログに書かれている通りですが、drawImageをする前にscaleで「端末幅(screen.width) / 320」に自動拡大されるのを元に戻してやる必要があります。
  • そうするには、「320 / screen.width」でscaleしたらいんじゃないかと思いますが、scaleが2回適用される問題があるので、2回適用されることを計算して平方根を指定する必要があります。
  • Android Eclairかどうか判定する処理を加えるとこんな感じでしょうか?
var image = new Image(); 
image.src = "http://www.google.co.jp/intl/en_com/images/srpr/logo1w.png"; 
image.onload =function(){ 
  var ctx = document.getElementById('test').getContext('2d'); 
    ctx.save();
    // Eclair判定
    if ( /Android\s2\.[0|1]/.test(navigator.userAgent)) {
        // 縮小
        var rate =  Math.sqrt(320 / screen.width);
        ctx.scale( rate, rate);
    }
  ctx.drawImage(image, 20, 20, 200, 100); 
  ctx.restore();
  ctx.fillRect(20, 150, 200, 100); 
};  

反転させたい

  • 上記の方法で勝手に拡大される問題については対応が可能になるのですが、自分のようにscale(-1, 1)で画像を反転させたいと思っていた人には困ったことになります。scaleが2回適用されるため、-1の平方根を取得する必要があります。。。
  • というわけで困ったなぁと思って悩んで自分が見出した解決策は、「Canvasでやらなくていいんじゃね?」ということでCSS3で実装する方法です。こんな感じ。
var img = document.createElement('img'); 
img.style.position = "absolute";
img.src = "http://www.google.co.jp/intl/en_com/images/srpr/logo1w.png"; 
img.width = 200;
img.height = 100;
img.style.top = "20px";
img.style.left = "20px";
img.style["-webkit-transform"] = "scale(-1, 1)"; 
document.getElementById('parent').appendChild(img);


こんな感じでブラウザ実装のバグに遭遇することもありますが、CanvasとかCSS3とか楽しいですね。スマートフォン向けだとI.Eを考慮しなくていいのが何より嬉しいです。

おまけ

  • PerlPlackというのを入れておくと、下記のコマンドでカレントディレクトリをルートとしたWebサーバーを立ち上げられるので便利です。(aliasに登録しておくといいかもですね。)
plackup --listen localhost:8080 -MPlack::App::File -e 'Plack::App::File->new(root => ".");'
ちなみに

canvasについてはここにちょっと書きました。