明日もデザインで食べていこう![05]関西オープンソースフォーラムのデモを解説/秋葉秀樹

投稿:  著者:  読了時間:15分(本文:約7,100文字)


みなさんどうもこんにちはでございます、秋葉でっす。
私こないだ、関西オープンソースフォーラムってカンファレンスに出演させてもらいました。一緒にいらっしゃったのはOperaのダニエル・デイビスさんとGoogleのデベロッパー、北村英志さん。どひゃ! 何をやった(やらされた)かというと、ライブコーディング。げっ!

で、HTML5のcanvas要素を使って、簡単なお絵かきアプリの制作をしたんです。わずか10数行のJavaScriptコードで、絵を書くツールを完成させるというものです。

出来上がりのサンプルがこちらです。白いキャンバスにマウスで絵が描けます。
▼簡単サンプル
< http://akibahideki.com/digicre/case_05/sample03.html >

ですが、ちょっと物足りないので、応用版としてもうちょっとコードを加えたサンプルはこちら。ドロップダウンの値(カラーの文字列)が変更されたら次から描く線の色が変わる、というもの。
▼応用サンプル(Safari, Chrome, Opera推奨!)
< http://www.akibahideki.com/blog/2010/11/06/kof03.html >

応用版を見てみると分かるとおり、HTMLのドロップダウン(<select>)で色を変えることができます。HTMLとJavaScriptの対話は簡単なんです。
さらに今回はスライダー(Firefoxが非対応)を使って、線の太さを変更できるようにしました。これ、マウスで左右にスライド出来ますよね?
実は<input>要素のtype属性を「range」にすると、こうなります。



【code】
<input type="range" value="1" min="1">

スライダーの場合、最低値と最大値を設定しないといけません。今回は、線の太さを1px〜30pxとしました。初期設定の最低値と初期値を、それぞれ「1」にしたければ、上記のようなコードを書けば良いのです。

さて、まずは「簡単サンプル」の方から作っていくことにしましょう。


●HTML5形式でマークアップしてみる。
HTML5の文書型宣言はとてもシンプル、以下の通り。

【code】
<!DOCTYPE html>

今回はだいぶ簡素化するので、HTMLは以下のように書いておきます。
【code】
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>お絵描きcanvas demo</title>
</head>
<body>
<canvas class="cvs" width="600" height="600"></canvas>
</body>
</html>

ここで登場する<canvas>。グラフィックやアニメーションを表現するために用意されたHTML5の新要素です。今回は600px四方の範囲を「お絵かきする範囲」としましょう。class名を付けたのは、あとでJavaScriptで扱いやすくするためです。
ここまでかけたら、後はJavaScriptを書いていきます。今回は<head>要素内に書いていく事にします。

【code】
<head>
<script>
var ctx;
//========= どこからでもアクセス出来る変数を作っておく。
function init(e) {
//========= HTML読み込み完了したら実行開始!
var canvas = document.getElementsByClassName("cvs")[0];
ctx = canvas.getContext("2d");
}
window.addEventListener("load", init, false);
//========= HTMLを全て読み込んだら、init()って関数を実行します。それまで待機。
</script>
</head>

本来なら、canvasをサポートしないブラウザへの対策をしないといけませんが、今回はcanvasサポートしているブラウザを前提としましょ。

まずwindow.addEventListener〜〜から書き始めます。
この()内で指定するinitってのが、その上に書いた関数です。HTMLというのは読み込み完了するまでにJavaScriptを実行してしまったら、HTMLを認識出来ないので、最初はこう書いといて、読み込み完了を待ちます。

var canvas = document.getElementsByClassName("cvs")[0];
の行ですが、最近JavaScriptで普及するであろう「getElementsByClassName()」を使ってみました。canvasにクラス名を付けたのはこのためだったんです。[0]としてるのは、同じクラス名の一番最初の要素だけ取得してね、という意味です。

canvasという要素に対して、そのまま絵を描くのではなく、canvas内の「2Dコンテキスト」というレンダリング領域に描画の命令をします。そのため、ctxというグローバルな変数を用意しておき、後から canvas.getContext("2d")します。さて、ようやくこれで変数ctxに対して命令を描く準備が出来ました。


●マウスが押された瞬間、線を引き始める

準備ができたところで、マウス操作に対する反応をどうするか? です。

function init(e) {
var canvas = document.getElementsByClassName("cvs")[0];
ctx = canvas.getContext("2d");
window.addEventListener("mousedown", mouseDownHandler, false);
//=========マウスが押された瞬間、mouseDownHandler()って関数を実行しましょ
}
function mouseDownHandler (e) {
ctx.lineTo(e.clientX, e.clientY);
//=========マウスの位置を取得して線を描くポイントを決めます。
ctx.stroke();
//=========線を引きなさい、という命令です。
}

さあ、これが出来たサンプルはこちらです、マウスをポンポンとクリックしてみてください。

▼サンプルURL
< http://akibahideki.com/digicre/case_05/sample01.html >


●直線じゃなく、曲線を描けるようにする

プログラムの流れを若干変えてみます。

(変更前、というか今の状態)
1. マウスが押されたら以前押された地点と直線で結ぶ

(変更後)
1. マウスが押された瞬間に「マウスが動くたびに処理する」
2. マウスが動くたびに以前の地点とを細かく結ぶ

まず、サンプルがこちら
▼サンプルURL
< http://akibahideki.com/digicre/case_05/sample02.html >

さらに、スクリプト全体のコードをおさらいするとこうなります。

【code】
<script>
var ctx;
function init(e) {
var canvas = document.getElementsByClassName("cvs")[0];
ctx = canvas.getContext("2d");
window.addEventListener("mousedown", mouseDownHandler, false);
//=========マウスが押された瞬間、mouseDownHandler()って関数を実行しましょ

}
function mouseDownHandler (e) {
window.addEventListener("mousemove", draw, false);
//=========マウスが動くたびにdraw()って関数を実行しましょ
}

function draw (e) {
ctx.lineTo(e.clientX, e.clientY);
//=========マウスの位置を取得して線を描くポイントを決めます。
ctx.stroke();
//=========線を引きなさい、という命令です。
}

window.addEventListener("load", init, false);
</script>

何となく、マウスで曲線も描けて、お絵かきツールっぽくなってきました。(よね?)


●マウスを離したら、線がつながらなくなるようにしたい

今描いていると、マウスを離しても、線がくっついてきます。これ、なんとかしたいですよね? というわけで、プログラムに流れを加えます。

(変更点)
1. マウスが離されたらどうする、という処理を加えます。
2. マウスが離されたら、draw()関数を呼ぶのを止めます。

ここまでやってみましょう。コードはこのように加えられます。

【code】

function init(e) {
//  (省略)
window.addEventListener("mouseup", mouseUpHandler, false);
//=========マウスが離された瞬間、mouseUpHandler()って関数を実行!
}

function mouseUpHandler (e) {
window.removeEventListener("mousemove", draw, false);
}

mouseUpHandler()って関数の中にremoveEventListener()って関数があります
が、これ、addEventListenerの逆の意味で、以下のような事が言えます。

addEventListener → 「マウスが動くタイミングを監視し続ける」
removeEventListener → 「マウスが動くタイミングを監視をヤメる」

つまり、draw関数を呼び続けるのをヤメてね、線を引っ張るのをヤメてね。と、言っているようなものです。

ところがっ・・・・!!! サンプルで試してください、これじゃダメだ!
▼サンプルURL
< http://akibahideki.com/digicre/case_05/sample03.html >

そうなんです、、、マウスを離したら、確かに線は追いかけてこなくなりましたが、次にマウスを押すと、線が引っ張られます。これはどういう現象かというと、Adobe Illustratorのペンツールを使った事がある方は分かりやすいと思うんですが、途中で作業をヤメない限り、点を打つと線が結ばれます。だから、一旦図形を(パスを)初期化してあげる事が必要です。

いくつかの方法がありますが、ここでは1行、
ctx.beginPath();
と、加えるだけで期待した結果が得られます。サンプルとコードは下記の通りです。
▼サンプルURL
< http://akibahideki.com/digicre/case_05/sample04.html >

【code】
function mouseUpHandler (e) {
ctx.beginPath();
window.removeEventListener("mousemove", draw, false);
}

文字通り、「パスを描き始める」ということですね。


●線の太さ、線の色、先端の形状などを思い通りに

自由に線を描く事が出来ました。しかし、極細線だし、なんだか先端が四角いし、色も黒だけだし。もっと自由に絵を描きたい場合、下記のような「プロパティ」も用意されています。

・lineWidth ... (線の幅を決めます)
・strokeStyle ... (線の色を決めます)
・lineCap ... (先端の形状を決めます、四角、丸など)

使ってみましょう。

【code】
function draw (e) {
ctx.lineWidth = 10;
ctx.strokeStyle = "#ff0000";
ctx.lineCap = "round";
ctx.lineTo(e.clientX, e.clientY);
ctx.stroke();
}

上記の場合だと、
・線の色は赤(#ff0000)
・線の幅は10px
・線の先端の形状はラウンド(丸形)


●完成

はい、シンプルですが、これでHTML5で作る「お絵かきアプリ」完成です。
サンプルとコードは以下の通りです。
▼サンプルURL
< http://akibahideki.com/digicre/case_05/sample05.html >

【code】

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>お絵描きcanvas demo</title>
<script>
var ctx;
function init(e) {
var canvas = document.getElementsByClassName("cvs")[0];
ctx = canvas.getContext("2d");
window.addEventListener("mousedown", mouseDownHandler, false);
window.addEventListener("mouseup", mouseUpHandler, false);
}

function mouseDownHandler (e) {
window.addEventListener("mousemove", draw, false);
}

function mouseUpHandler (e) {
ctx.beginPath();
window.removeEventListener("mousemove", draw, false);
}

function draw (e) {
ctx.lineWidth = 10; // 線の幅を10px。
ctx.strokeStyle = "#ff0000"; // 線の色は赤。
ctx.lineCap = "round"; // 線の先端を丸型(ラウンド)に。
ctx.lineTo(e.clientX, e.clientY);
ctx.stroke();
}

window.addEventListener("load", init, false);

</script>
</head>
<body>
<canvas class="cvs" width="600" height="600"></canvas>
</body>
</html>


●応用とまとめ

今回は線だけですが、塗りを指定するために、stroke()だったところにfill()を入れると以下のようなことが可能です。

▼サンプルURL
< http://akibahideki.com/digicre/case_05/sample_ok.html >

今回はかなりシンプルですが、これを応用するともっと機能性の高いお絵かきアプリの制作も可能です。私が思うに、以下のような事も可能ではないかと。

・色はCSSの値(#ff0000や「red」など)の指定が可能なため、カラーチップを作ればフルカラーも可能。
・png画像を読み込み可能なため、「ブラシツール」のような形状を持ったブラシで今回のような事も可能。
・ベジェ曲線を書く機能を備えているため、ビットマップ系のみならず、ベクター系も可能。
・<img>や<video>の画像を読み込む事が可能なため、写真をレタッチする事も可能。

可能性は大きいんですが、仕様はシンプルなのでこれらを作成するにはとても労力がかかりそうです。
正直、Flashのような「重ね順」がcanvasには存在しないので、レイヤー階層を持つPhotoshopみたいなアプリを作るとなると、一度レイヤー全部のビットマップを記憶して管理する、といった作業が必要でしょう。今、世界中でこのあたりのライブラリが作られているようです。その辺もまたいずれ掘り下げていきたいと思います。
では〜〜〜。

【あきば・ひでき】hidetaro7@gmail.com
< http://www.akibahideki.com/blog/ >

テクニカルディレクター・デザイナー。DTP黎明期からグラフィックデザインを学び、東京都営団地下鉄など交通広告を多数手がける。同時に音楽活動も活発に行い、西日本半全国ツアーなどを展開、某専門学校のテーマソングを作詞作曲、編曲から楽器全てを演奏してレコーディングするなど、マルチなクリエイティブ活動も。最近では東京と大阪の教育施設などで講師業をも務める。
HTML CSS JavaScript Flash ActionScript 3DCG Movie DTP GraphicDesign...多種スキルを持つ。Web標準技術だけに執着せず、全てのメディアで説得力のある表現にチカラを注ぎたい、そんな仕事をしたい。