iPhone, Android(desire) ブラウザのhtml5対応状況

手元にあるスマートフォンhtml5対応状況を、http://html5test.com/ で確認したのでメモ。

(※) ユーザエージェントは、「javascript:alert(navigator.userAgent)」で表示されたもの

iPhone4 iPhone3G Android2.1
Parsing rules <!DOCTYPE html> triggers standards mode YES YES YES
HTML5 tokenizer NO NO NO
HTML5 tree building NO NO NO
SVG in text/html NO NO NO
MathML in text/html NO NO NO
Canvas canvas element YES YES YES
2D context YES YES YES
Text YES YES YES
Video video element YES YES YES
Subtitle support NO NO NO
Poster image support YES YES YES
MPEG-4 support YES YES NO
H.264 support YES YES NO
Ogg Theora support NO NO NO
WebM support NO NO NO
Audio audio element YES YES YES
PCM audio support YES YES NO
MP3 support YES YES NO
AAC support YES YES NO
Ogg Vorbis support NO NO NO
WebM support NO NO NO
Local devices device element NO NO NO
Elements Embedding custom non-visible data NO NO NO
Section elements 6 out of 7 NO NO
Grouping content elements NO NO NO
Text-level semantic elements NO NO NO
hidden attribute NO NO NO
Scroll into view YES YES YES
contenteditable attribute YES YES YES
Forms input element types YES 5 out of 13 6 out of 13
input element attributes 8 out of 10 1 out of 10 5 out of 10
Other form elements NO NO 1 out of 5
Form validation YES NO NO
User interaction Drag and drop YES NO YES
Undo history NO NO NO
Session history YES NO NO
Text selection YES YES YES
Microdata Microdata NO NO NO
Web applications Application Cache YES YES YES
Custom scheme handlers YES NO NO
Custom content handlers YES NO NO
Geolocation Geolocation YES YES YES
WebGL 3D context NO NO NO
Communication Cross-document messaging YES YES YES
WebSocket NO NO NO
Server-Sent Events YES NO NO
Files FileReaderAPI NO NO NO
Storage Session storage YES YES YES
Local storage YES YES YES
IndexedDB NO NO NO
Web SQL Database YES YES YES
Workers Web Workers NO NO YES

WebSocket。。。

iPhone Safariだとaudio/videoタグのautoplay, autobuffer属性が効かない

Resources - Safari - Apple Developer
User Control of Downloads Over Cellular Networks」の部分

In Safari on iPhone OS (for all devices, including iPad), where the user may be on a cellular network and be charged per data unit, autobuffering and autoplay are disabled. No data is loaded until the user initiates it. This means the JavaScript play() and load() methods are also inactive until the user initiates playback, unless the play() method is triggered by user action. In other words, a user-initiated Play button works, but an onLoad play event does not.

「携帯が従量課金とかの可能性があるから、autoplayとautobufferは無効にしてるよ。
ユーザが意図して再生を開始するまで、データはロードされないよ。
それは、ユーザのアクションをトリガーにして再生を開始するまでは、JSのplay()とかload()も機能しないってことを意味するよ。
つまり、ユーザによって開始されたplay()は動作するけど、onloadとかのイベントだと動作しないよ。」
って感じでしょうか。


たしかに、実際以下のようなhtmlを書いても、再生されなかった。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>html5 audio test on iPhone</title>
</head>
<body>

<audio src="music.mp3" autoplay></audio>

</body>
</html>


でも、下のようなhtmlは再生されてはいけないはずなのだが再生された。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>html5</title>
<script type="text/javascript"><!--
function onload_play() {
    var aud = document.getElementById('aud');
    aud.load();
    aud.play();
}
//--></script>
</head>
<body onload="onload_play();">

<audio id="aud" src="music.mp3"></audio>

</body>
</html>

これがバグなのかどうかはわからない。


が、いずれにせよ、サイレントモードを無視して再生されるので、

音を鳴らすのは微妙。

なんかいい方法はないのか。。。

html5 の File API を使って、ローカルのテキストファイルを読み込んでみる

とっても簡単です(Firefox 3.6 で動作を確認)。

html

fileのアップロードフォームにonchangeを仕込んでおきます。

あとは、読み込んだファイルの内容を表示するtextareaを用意。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>

<input type="file" onchange="read(this)" />

<hr />
<textarea id="text" cols="80" rows="20" wrap="off"></textarea>

</body>
</html>

javascript

function read (ele) {
    if (!ele.files.length) return;

    var file = ele.files[0];
    if (!/^text\//.test(file.type)) return;  // text/plain, text/html, ...

    var fr = new FileReader();
    fr.onload = function () {
        document.getElementById('text').value = fr.result;  // 読み込み結果をtextareaに
    };
    fr.readAsText(file);  // ファイルをテキストとして読み込む
}

readAsTextの2つ目の引数で文字コードを指定できます*1が、
指定しなくても上手いこと推定してくれるっぽいです。
UTF-8, SJIS, EUC は普通に読み込めました。)


file フォームに multiple をセットしておけば、
複数ファイルの中身をマージした結果とかも簡単に出せちゃいそうですね

*1:fr.readAsText(file, "UTF-8"); とかで

html5の File API を使って、アップロード無しで画像プレビュー


画像をプレビューするために、サーバへアップロードする必要がなくなります*1

html

fileのアップロードフォームにonchangeを仕込んでおきます。
あとは、プレビュー表示用の要素だけ準備。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>

<form>
  <input type="file" name="file" onchange="preview(this)" />
</form>

<hr />
<b>preview:</b><br />
<div id="preview_field"></div>

</body>
</html>

javascript

function preview(ele) {
    if (!ele.files.length) return;  // ファイル未選択
    
    var file = ele.files[0];
    if (!/^image\/(png|jpeg|gif)$/.test(file.type)) return;  // typeプロパティでMIMEタイプを参照

    var img = document.createElement('img');
    var fr = new FileReader();
    fr.onload = function() {
        img.src = fr.result;  // 読み込んだ画像データをsrcにセット
        document.getElementById('preview_field').appendChild(img);
    }
    fr.readAsDataURL(file);  // 画像読み込み

    // 画像名・MIMEタイプ・ファイルサイズ
    document.getElementById('preview_field').innerHTML =
        'file name: ' + file.name + '<br />' +
        'file type: ' + file.type + '<br />' +
        'file size: ' + file.size + '<br />';
}

スバラシイ。

*1:Firefox 3.6で動作を確認

html5で複数ファイルアップロード&保存

とても簡単。

html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>

<form action="save.cgi" method="POST" enctype="multipart/form-data">
  <input type="file" name="files" multiple />
  <input type="submit" value="submit" />
</form>

</body>
</html>

input type="file" で multiple属性を追加するだけ。

save.cgi (perl)

#!/usr/bin/perl

use strict;
use warnings;
use CGI;
use File::Copy;

my $SAVE_DIR = '/save/dir';  # 保存するディレクトリのパス

my $q = CGI->new;
my @files = $q->param('files');
for my $filename (@files) {
    copy($q->tmpFileName($filename), "$SAVE_DIR/$filename") or die $!;
}

exit;

今までは、複数ファイルが選択できるアップローダーにはFlashが必要だったから、これは非常に便利。

canvasに描いた絵(画像)をサーバに保存

canvasに描かれたデータを取得するには、canvas.toDataURL() を使います。
toDataURL()で得られるのは、base64エンコードされた画像情報なので、
それをサーバに送って、base64デコードして保存という流れです。


toDataURL()して、そのデータをPOSTする部分のjavascript (ajax部分が面倒なので、prototype.jsを使用)

function saveImage() {
    var imgdata = $('canvas_id').toDataURL();  // デフォルトだとpng, 引数でjpegとかも可能
    imgdata = imgdata.replace('data:image/png;base64,', '');  // 頭のいらない部分を落とす

    new Ajax.Request(<画像保存CGIのURL>, {
        parameters: 'img=' + imgdata,  // 画像データを送信
        onComplete: function(res) {  // callback 別になくてもよいが。
            if (res.responseText != 'ok') alert('error');
        }
    });
}


サーバ側(画像保存CGI

#!/usr/bin/perl

use strict;
use warnings;
use CGI;
use MIME::Base64;

my $q   = CGI->new;
my $img = $q->param('img');  # base64エンコードされた画像データを取得

my $res_msg = 'error';
if ($img) {
    open(my $fh, '>', 'image.png') or die $!;  # 保存するファイル名でopen
    print $fh MIME::Base64::decode($img);  # base64デコード
    close($fh);

    $res_msg = 'ok';
}

print "Content-type:text/plain\n\n";
print $res_msg;
exit;

これで、image.pngcanvasの画像が保存されます。

origin-clean flag

canvasにはセキュリティの面から、origin-cleanフラグというものが存在しているらしいです。
このフラグが false になっている場合、toDataURL()は「security error」となって、失敗します。


これに関する原文は、以下です。
http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#security-with-canvas-elements


canvasは、最初はorigin-cleanフラグがtrueにセットされていますが、
いくつか特定のアクションをするとorigin-cleanフラグがfalseになります。


例えば、以前の記事 でやった画像の読み込み(drawImage)とかで、
その画像が canvasを表示しているページと同じドメインの画像なら toDataURL()できますが、
ドメインの画像をcanvasに描画してしまうと、origin-cleanフラグがfalseになって、toDataURL()できなくなります。


なので、外部の画像を読み込ませて、お絵かきさせて、サーバで保存とかしたい場合は、
一度サーバ側で画像をダウンロードして、それをcanvasに描画する必要がありそうです。

canvasで簡単なお絵かきをしてみたメモ

タイトルのまんまです。
Firefox/Chromeあたりで動きます。

デモページ

html

canvasを用意しとくだけです。
canvas内の座標を取るときに、layerX/Y で取りたいので、styleにposition:relativeを当てておきます。
(positionが当たってないと、layerX/Yは pageX/Y の値と等しくなるらしいので。
positionを当てたくないなら、pageX/Yからcanvasのオフセットを引いたりすることになるんでしょうか。)

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>

<canvas id="c" style="position:relative; border:3px solid #999" width="600" height="480"></canvas>

</body>
</html>

javascript

var cEle, cCtx;
var drawing = false;  // ドラッグ中フラグ

window.addEventListener('load', function() {  // window onload
    cEle = document.getElementById('c');
    cCtx = cEle.getContext('2d');

    // canvasの設定
    cCtx.lineJoin    = 'round';  // 角を丸く
    cCtx.lineCap     = 'round';  // 線の終端を丸く
    cCtx.lineWidth   = 5;        // 線の幅
    cCtx.strokeStyle = '#00F';   // 線の色

    // event
    cEle.addEventListener('mousedown', start, false);  // canvas上のmousedownイベントでstart()を呼び出す
    cEle.addEventListener('mousemove', move, false);   // canvas上のmousemoveイベントでmove()を呼び出す
    window.addEventListener('mouseup', stop, false);   // window上のmouseupイベントでstop()を呼び出す
}, false);

function start(event) {
    cCtx.beginPath();  // サブパスリセット

    cCtx.moveTo(event.layerX, event.layerY);  // 初期座標を指定
    drawing = true;  // ドラッグ中フラグを立てる

    move(event);  // ドラッグせずにすぐmouseupした場合に、点を書くため
}
function move(event) {
    if (!drawing) return;

    cCtx.lineTo(event.layerX, event.layerY);  // 直前の座標と現在の座標を直線で繋ぐ
    cCtx.stroke();  // canvasに描画
}
function stop(event) {
    if (!drawing) return;

    cCtx.closePath();  // サブパスを閉じる
    drawing = false;   // ドラッグ中フラグを落とす
}

stop()をcanvasでなくwindowに登録しているのは、
canvas上でmousedownし、ドラッグしたままcanvas外まで出てからmouseupした際にもちゃんと動作するためです。


クリックした場合、Firefoxでは点が描画されますが、Chromeでは描画されません。
また、FirefoxでもlineCapがroundでないと点は描画されません。


おまけ

ネット上の画像をcanvasに描画できる機能を追加してみます。

html

画像のURLを入力するフォームを追加

<form onsubmit="loadImage(this.image_url.value); return false;">
  Image URL:
  <input type="text" size="80" name="image_url" />
  <input type="submit" value="load" />
</form>
javascript
function loadImage(imgUrl) {
    var img = new Image();
    img.src = imgUrl + '?' + (new Date/1);  // 一応、キャッシュ対策とかしとく
    img.onload = function() {
        cCtx.drawImage(img, 0, 0);  // 画像が読み込めたらcanvasに描画
    }
    img.onerror = function() {
        alert('image load error');
    }
}

リサイズとか何もせずにただ描画しているので、はみ出した部分は描画されません。


Chromeは無理ですが、Firefoxなら [右クリック] --> [名前を付けて画像を保存] で
canvasの画像をローカルに保存できます。


(※) なお、紹介したコードはわかりやすく書くために、グローバル変数とか気にせず使ってますが、
本当ならクラスとか作って、その中に閉じ込める方がベターです。


各メソッドやプロパティなどの詳細は、Canvasリファレンス - HTML5.JP を参照。