2014/04/06

かずー氏アイコンジェネレータをHTML5で作ってみた

HTML/CSS/JavaScriptを駆使した画像の重ね方 | DevAchieveという記事、
かずー氏クラスタアイコンの作り方の記事を1年前くらいに書いていました。
そこで Canvas を使用してかずー氏 (@kazoo04)のアイコン背景と
@wada811 のアイコンを合成できていたので
ユーザーがアップロードした画像を読み込んで
同じように Canvas に表示してあげて合成すれば
かずー氏アイコンジェネレータになるのではないか
というのがこのプロジェクトの始まりでした。

そして、途中で開発が止まるなど紆余曲折がありましたがついに完成しました!
かずー氏アイコンジェネレータ

わずか 3STEP で、かずー氏アイコンを作成できるという優れものです。
(本当はページ開いた瞬間にファイル選択が出て、選択したら自動ダウンロードということもできます。)
今回サーバいらずで、クライアントサイドの技術のみでかずー氏アイコンを生成することができます。
この手のちょっとしたネタサイト作るためにサーバの用意とかお金かけるとかしたくない人におすすめです。

技術的な話

Drag and Drop API

実は画像をドラッグ・アンド・ドロップでアップロードすることが可能です。
デザイン上、ドラッグ・アンド・ドロップができるとわかるようになっていないので
何かいい感じのデザインになる Pull Request を待っています。

Drag and Drop API 機能の実装は簡単で、ドラッグ・アンド・ドロップ系のイベントを適切に処理するだけです。
<canvas id="jsKazoo04Icon"></canvas>
$(document).ready(function(){
    // ...
    addDropListener('jsKazoo04Icon');
});

function addDropListener(id){
    var jsDropBox = document.getElementById(id);
    document.addEventListener('dragover', dropNone);
    document.addEventListener('drop', dropNone);
    jsDropBox.addEventListener('dragover', dropCopy);
    jsDropBox.addEventListener('drop', drop);
}
ブラウザに画像ファイルをドロップすると画像をそのまま表示してしまうので処理を止めています。
document.addEventListener('dropover', dropNone); 
document.addEventListener('drop', dropNone);
function dropNone(event) {
    event.stopPropagation(); // イベントのバブリング(親要素への伝搬)を停止する
    event.preventDefault(); // ブラウザのデフォルトの処理(画像の表示)を防ぐ
    event.dataTransfer.dropEffect = 'none'; // D&D中のファイルにアイコンがつかないようにする
}
ドロップを受け付ける要素に addEventListener でリスナーを登録します。
jsDropBox.addEventListener('dragover', dropCopy);
jsDropBox.addEventListener('drop', drop);
function dropCopy(event) {
    event.stopPropagation(); // イベントのバブリング(親要素への伝搬)を停止する
    event.preventDefault(); // ブラウザのデフォルトの処理(画像の表示)を防ぐ
    event.dataTransfer.dropEffect = 'copy'; // D&D中のファイルにプラスアイコンがつくようにする
}

function drop(event){
    // 画像ビューアとして開かないようにする
    dropCopy(event);
    // ドロップされたファイルを取得
    var file = event.dataTransfer.files[0];
    if(file.type !== 'image/png'){
        alert('PNG画像のみを受け付けています。');
        return;
    }
    handleFile(file);
}

参考
[HTML5] Drag & Drop API おさらい 「ファイルの DnD」 | Developers.IO

JavaScript から参照する要素には js プレフィックスをつけたほうがいいよという話。
CSSのセレクタ部分(IDやCLASS)にハイフンとか使われるの好きじゃないなー。ボクはあまり好きじゃないなー - latest log

File API

input[type="file"] で画像アップロードを受け付け、
アップロードされた画像をプレビュー表示する際に使用しました。
<input type="file" id="jsUpload" class="upload" onchange="upload(this.value)" accept="image/*" />

まず、画像のみを受け付けるためには accept="image/*" を指定します。
参考
HTML5時代のinput要素のaccept属性について | 富永日記帳
4.10.5.1 States of the type attribute — HTML Standard

input[type="file"] で画像アップロードした際のイベントを取得するには onchange="upload(this.value)" を指定します。
今回は fileName は未使用ですが、何かに使えるかも?
スマホの場合はセキュリティのためダミーのパスが入るので使えないかも?
function upload(fileName){
    var files = document.getElementById('jsUpload').files;
    var file = files[0];
    handleFile(file);
}
File API で画像を読み込みます。
function handleFile(file){
    var fileReader = new FileReader();
    fileReader.readAsDataURL(file);
    // ファイル読み込み失敗時
    fileReader.onerror = function(e){
        alert('画像の読み込みに失敗しました。');
    };
    // ファイル読み込み完了時
    fileReader.onload = function(event){
        var image = new Image();
        image.src = event.target.result;
        image.onload = function(){
            // 画像が読み込み完了したら Canvas に描画
            var canvas = document.getElementById('jsKazoo04Icon');
            var ctx = canvas.getContext('2d');
            ctx.drawImage(image, 0, 0);
        };
    };
}

参考
Using files from web applications | MDN

Canvas を toDataURL で Data URL 形式の画像に変換する

解説はしていないがHTML/CSS/JavaScriptを駆使した画像の重ね方 でもできていたもの。
Canvas の描画内容を Data URL に変換して画像タグを貼り付けるだけです。
function generateKazoo04Icon(){
    var canvas = document.getElementById('jsKazoo04Icon');
    var ctx = canvas.getContext('2d');

    var dataURL = canvas.toDataURL('image/png');
    var imgTag = '<img src="' + dataURL + '" width="' + canvas.width + '" height="' + canvas.height + '" alt="かずー氏背景合成画像">';
    $('#jsKazoo04Icon').after(imgTag).remove();
    // ...
}

Data URL 形式の画像を Blob に変換して Blob URL 形式に変換

Canvas に toBlob もあるのですが、ほとんどのブラウザで実装されていないようです。
仕方ないので dataURL から Blob に変換する関数をかませて
window.URL.createObjectURL で Blob URL 形式に変換します。
function generateKazoo04Icon(){
    // ...
    var blob = Base64toBlob(dataURL);
    window.URL = window.URL || window.webkitURL;
    $('#jsDownload').attr("href", window.URL.createObjectURL(blob));
}

function Base64toBlob(_base64){
    var i;
    var tmp = _base64.split(',');
    var data = atob(tmp[1]);
    var mime = tmp[0].split(':')[1].split(';')[0];

    //var buff = new ArrayBuffer(data.length);
    //var arr = new Uint8Array(buff);
    var arr = new Uint8Array(data.length);
    for (i = 0; i < data.length; i++) {arr[i] = data.charCodeAt(i);}
    var blob = new Blob([arr], { type: mime });
    return blob;
}

参考
Canvasの画像(スクリーンショット)をローカルダウンロードします。 - jsdo.it - Share JavaScript, HTML5 and CSS
Base64をblobオブジェクトに変換します。 - jsdo.it - Share JavaScript, HTML5 and CSS
URL.createObjectURL - Web API Interfaces | MDN

だいたいオッケー、でも…

Drag and Drop による画像アップロードがわかりにくい点、
読み込んだ画像を移動、回転、拡大・縮小できない点がユーザー的に微妙ですね!
さぁあなたの Pull Request を待っています!

ちなみに「読み込んだ画像を移動、回転、拡大・縮小できない点」は
Fabric.js Javascript Canvas Libraryというものを使えば解決できます。

まだ時間をかければ実装はできますが、とりあえずリリースすることが大事ってばっちゃが言ってた。

タグ(RSS)