今回はHTML, JavaScriptに少し慣れてきたかたに向けて、HTMLのcanvas要素を使ってお絵かきアプリが簡単に作れるということを紹介したいと思います。
今回作るものは次のものになります。
実際に今回の「お絵かきアプリ」を使ってみたい方は「こちら(Github pagesで公開)」からアクセスしていただければ試すことが出来ます。(パソコンからのみ利用可)
今回は「お絵かきアプリ」の作り方の紹介記事になりますが、canvas要素を使うことで次のような希望も実現することが出来ます。
最終的にはゲームを作ってみたいなー。
画像を加工出来るアプリ作ってみたいなー。
今回の記事では次のような内容で話を進めていきます。
- HTMLのcanvasを使って出来ること
- canvasとJavaScriptをつかったお絵かきアプリの作り方
目次
HTMLのcanvasを使って出来ること
HTMLのcanvasを使うことで出来ることはいろいろあります。
- 今回紹介するお絵かきアプリ
- ゲーム
- Webビデオ使ったアプリ
Webビデオ通話アプリの場合は、canvasだけではなく、パソコンのカメラ・マイクの操作と組み合わせることで実装が可能です。
当たり判定は、重力を考慮した複雑なゲームを作る場合は、数学や物理の概念が必要になってくる場面が出てきます。
そのため、「Webビデオ通話アプリやゲームを作るのはハードルが高いなー」と思っている方は、「お絵かきアプリ」から入ってcanvasで出来ることに徐々に慣れていくと良いでしょう。
今回作る「お絵かきアプリ」はHTML, JavaScriptの基本を学習した方であれば数時間で実装できると思います。
それでは次にお絵かきアプリの作り方について解説していきます。
canvasとJavaScriptをつかったお絵かきアプリの作り方
今回は最低限の機能のみを実装します。
- canvas上でマウスをドラッグしたら線を引く
- 線の色は黒のみ
- 「全消し」ボタンを押したらcanvasを真っ白の状態に戻す
次回以降の記事で次のような機能を追加する予定です。
- 線の太さを変更できる
- 色を変更できる
- 消しゴム機能
今回使用するソースコードは以下のGithubのレポジトリにアップしているので、参考にしたい方はレポジトリのソースコードをダウンロードしていろいろいじってみると良いでしょう。
- 今回の記事用に作成したソースコード: tsuyopon-xyz/drawing_app_part1
今回使用するファイルは2ファイルのみです。(HTMLファイル1つ、JavaScriptファイル1つ)
- index.html
- main.js
それでは実際にそれぞれのコードを見ながら解説していきます。
HTMLファイル
HTMLは中身をみていただくとわかると思いますが、いたってシンプルです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>お絵かきアプリ</title> </head> <body> <h1>お絵かきアプリ</h1> <canvas id="draw-area" width="400px" height="400px" style="border: 1px solid #000000;"></canvas> <div> <button id="clear-button">全消し</button> </div> <script src="./main.js"></script> </body> </html> |
上記のコードで主に行っていることは次の3つです。
- 絵(線)を書くためのcanvasを用意
- 絵(線)を消すためのボタンを用意
- canvas上の絵(線)を描画・削除するための機能を実装するJavaScriptファイルの読み込み
しかし、ここで1点注意が必要です。
①の「絵(線)を書くためのcanvasを用意」でcanvasを用意しているのですが、width(横幅)、height(高さ)をstyle(CSS)で定義するだけだと次のようにマウスの位置に対して線を引く位置がおかしなことになります。
この問題が起きてしまう原因は、canvasのデフォルトサイズがwidth(横幅)300px、height(高さ)150pxだからです。
style(CSS)を使ってwidthとheightを400pxにした場合、見た目上は意図したサイズになっていたとしても、canvasが内部で保持しているサイズはwidth300px、height150px のままです。
つまり、比率が異なるため発生していた問題になります。
次の記事でも詳細が説明されているので、参考にしていただけたらと思います。
この問題を解決するためには次の2通りがあります。
- HTMLのcanvas要素に直接width, heightを指定する
- JavaScript側でwidth, heightを指定する
今回はお絵かきアプリの作り方の説明がテーマのため、①のcanvas要素に直接「width=”400px”」「height=”400px”」指定しています。(13, 14行目)
②のやり方は先程の記事(「canvasのサイズ指定」)にてやり方が説明されていますのでそちらを参照していただけたらと思います。
それでは次に実際にcanvasを使って絵を書く処理を実装しているJavaScriptのコードを確認しましょう。
JavaScriptファイル
JavaScriptのコードは次のとおりです。
それぞれのコードが何を行っているのかコメントを細かく書いているので、コードが長めに感じられるかもしれませんが、コメントを除くと半分くらいの50~60行ほどのコードとなります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
// ページの読み込みが完了したらコールバック関数が呼ばれる // ※コールバック: 第2引数の無名関数(=関数名が省略された関数) window.addEventListener('load', () => { const canvas = document.querySelector('#draw-area'); // contextを使ってcanvasに絵を書いていく const context = canvas.getContext('2d'); // 直前のマウスのcanvas上のx座標とy座標を記録する const lastPosition = { x: null, y: null }; // マウスがドラッグされているか(クリックされたままか)判断するためのフラグ let isDrag = false; // 絵を書く function draw(x, y) { // マウスがドラッグされていなかったら処理を中断する。 // ドラッグしながらしか絵を書くことが出来ない。 if(!isDrag) { return; } // 「context.beginPath()」と「context.closePath()」を都度draw関数内で実行するよりも、 // 線の描き始め(dragStart関数)と線の描き終わり(dragEnd)で1回ずつ読んだほうがより綺麗に線画書ける // 線の状態を定義する // MDN CanvasRenderingContext2D: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin context.lineCap = 'round'; // 丸みを帯びた線にする context.lineJoin = 'round'; // 丸みを帯びた線にする context.lineWidth = 5; // 線の太さ context.strokeStyle = 'black'; // 線の色 // 書き始めは lastPosition.x, lastPosition.y の値はnullとなっているため、 // クリックしたところを開始点としている。 // この関数(draw関数内)の最後の2行で lastPosition.xとlastPosition.yに // 現在のx, y座標を記録することで、次にマウスを動かした時に、 // 前回の位置から現在のマウスの位置まで線を引くようになる。 if (lastPosition.x === null || lastPosition.y === null) { // ドラッグ開始時の線の開始位置 context.moveTo(x, y); } else { // ドラッグ中の線の開始位置 context.moveTo(lastPosition.x, lastPosition.y); } // context.moveToで設定した位置から、context.lineToで設定した位置までの線を引く。 // - 開始時はmoveToとlineToの値が同じであるためただの点となる。 // - ドラッグ中はlastPosition変数で前回のマウス位置を記録しているため、 // 前回の位置から現在の位置までの線(点のつながり)となる context.lineTo(x, y); // context.moveTo, context.lineToの値を元に実際に線を引く context.stroke(); // 現在のマウス位置を記録して、次回線を書くときの開始点に使う lastPosition.x = x; lastPosition.y = y; } // canvas上に書いた絵を全部消す function clear() { context.clearRect(0, 0, canvas.width, canvas.height); } // マウスのドラッグを開始したらisDragのフラグをtrueにしてdraw関数内で // お絵かき処理が途中で止まらないようにする function dragStart(event) { // これから新しい線を書き始めることを宣言する // 一連の線を書く処理が終了したらdragEnd関数内のclosePathで終了を宣言する context.beginPath(); isDrag = true; } // マウスのドラッグが終了したら、もしくはマウスがcanvas外に移動したら // isDragのフラグをfalseにしてdraw関数内でお絵かき処理が中断されるようにする function dragEnd(event) { // 線を書く処理の終了を宣言する context.closePath(); isDrag = false; // 描画中に記録していた値をリセットする lastPosition.x = null; lastPosition.y = null; } // マウス操作やボタンクリック時のイベント処理を定義する function initEventHandler() { const clearButton = document.querySelector('#clear-button'); clearButton.addEventListener('click', clear); canvas.addEventListener('mousedown', dragStart); canvas.addEventListener('mouseup', dragEnd); canvas.addEventListener('mouseout', dragEnd); canvas.addEventListener('mousemove', (event) => { // eventの中の値を見たい場合は以下のようにconsole.log(event)で、 // デベロッパーツールのコンソールに出力させると良い // console.log(event); draw(event.layerX, event.layerY); }); } // イベント処理を初期化する initEventHandler(); }); |
実際にこのコードで行っていることを細かく説明するのは以下の理由により割愛します。
- コメントで説明している内容と重複するため
- コードと記事の文章を行ったり来たりで読むのが大変になるため
その代わりにこちらのコードで行っていることをブロックに分けて説明します。
- 4~12行目: 絵を書くために必要な情報の定義を行っている
- 15~99行目: お絵かきアプリに必要な機能(関数)の定義を行っている
- 102行目: マウスイベント・クリックイベントの初期化を行っている
4~12行目: 絵を書くために必要な情報の定義を行っている
ここでは、canvasを使って絵を書くために必要な情報の取得や絵を書く際に必要となる設定値の用意しています。
- canvas: HTMLのcanvas要素のDOMを取得
- context: canvasに絵を書くために必要な情報
- lastPosition: 前回のマウスの位置情報。線を書く際は前回のマウス位置と現在のマウス位置を使って直線を引く。
- isDrag: マウスをドラッグ(クリックしっぱなし)時のみ絵を書けるようにするためのフラグ
15~99行目: お絵かきアプリに必要な機能(関数)の定義を行っている
実装されている関数は次のとおりです。
- draw関数: canvas上に線を引く(絵を書く)処理を行う
- clear関数: canvas上の線(絵)を全て削除する
- dragStart: 線(絵)の書き始めに必要な情報をセットする
- dragEnd: 線(絵)を書き終わった後に必要な情報をセットする
- initEventHandler: イベント処理の定義を行う
上記のように行う処理ごとに関数を分けることによって、「1つの関数では1つのことだけ行う」と言ったように機能を分離でき管理しやすいコードになります。
102行目: マウスイベント・クリックイベントの初期化を行っている
こちらはinitEventHandler関数を呼び出しているだけで、「initEventHandler」の関数を用意しないで、次のように直接イベントの定義を実装しても問題ないです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
function dragEnd(event) { // 線を書く処理の終了を宣言する context.closePath(); isDrag = false; // 描画中に記録していた値をリセットする lastPosition.x = null; lastPosition.y = null; } const clearButton = document.querySelector('#clear-button'); clearButton.addEventListener('click', clear); canvas.addEventListener('mousedown', dragStart); canvas.addEventListener('mouseup', dragEnd); canvas.addEventListener('mouseout', dragEnd); canvas.addEventListener('mousemove', (event) => { // eventの中の値を見たい場合は以下のようにconsole.log(event)で、 // デベロッパーツールのコンソールに出力させると良い // console.log(event); draw(event.layerX, event.layerY); }); |
まとめ
あらためてcanvasで出来ることをまとめたいと思います。
- 今回紹介したお絵かきアプリ
- ゲーム
- Webビデオ使ったアプリ
今回は「お絵かきアプリ」のベースの作り方をサンプルコードを使って説明しました。
次回は今回作った「お絵かきアプリ」に「色を変更できる機能」、「線の太さを変えられる機能」などを追加するために必要な実装を解説します。
今回使ったソースコードと、実際に動作しているお絵かきアプリにリンクを以下に貼っておきます。(パソコンでのみ絵を書くことが出来ます。)
- ソースコードが置かれているGithubレポジトリ
- 動作確認できるWebページ(パソコンでのみ利用可)