JavaScript - お絵描きアプリの作り方【基本編】

JavaScript でお絵描きしよう

今回は、JavaScript を使ってキャンバスに自由に描画する方法を紹介します。マウスやタッチパッドなどのポインティングデバイスでお絵描きできるシンプルなアプリを作ってみましょう。

今回の【基本編】では、線を描くことと描いたものを消すことができるだけの、単純なお絵描きアプリを作ります。 【機能追加編】では、線の太さや色を選べるように機能を追加していきます。

この記事を読むと分かること

  • mousedownイベント
  • mousemoveイベント
  • mouseupイベント
  • function(e)
  • offsetXプロパティ
  • offsetYプロパティ

サンプルプロジェクト

最初に、今回作成するお絵描きアプリの動きを確認しておきましょう。

  • キャンバス内でマウスボタン(タッチパッドなど)を押して、描画を開始する
  • マウスを押下したまま動かすと、描画される
  • マウスボタンを離すと、描画されない
  • 消去ボタンを押すと、キャンバスがクリアされる
お絵描きアプリのサンプル

HTML

まずは HTML です。

  • 描画エリア drawing-area <canvas> を作成し、width 属性と height 属性でキャンバスのサイズを指定します。
  • 描画したものを全消去できるように、消去ボタン clear-button <button> を作ります。
<!--キャンバス 描画エリア-->
<canvas id="drawing-area" width="500" height="400"></canvas>
<!--消去ボタン-->
<button id="clear-button">Clear</button>

JavaScript

続いて JavaScript です。JavaScript では、キャンバスに線を描画できるようにコーディングしていきます。もし、キャンバスに描画する基本的な方法が分からない場合は、以下の記事から先にご覧ください。

JavaScript - Canvas に図形を描く方法
HTML の canvas 要素を利用すると、JavaScript でグラフィックを描くことができます。基本的なメソッドを学んで、四角形や三角形、円やテキストなど簡単な図形を描画できるようになりましょう。
JavaScript - 図形に色やグラデーションを設定する方法
JavaScript で描画する図形にスタイルを設定するためのプロパティや、グラデーションカラーを作成するメソッドを紹介します。レインボーカラーや図形を立体的に見せるためのサンプルコードも掲載しているので参考にしてください。

要素を取得する

では、キャンバスに描画する準備をしていきましょう。

  • 描画エリアを querySelector() メソッドで取得し、変数 canvas に保存します。
  • キャンバスの 2d コンテキストを取得して変数 ctx に格納し、描画するためのメソッドやプロパティを利用できるようにします。
  • 消去ボタンを querySelector() メソッドで取得し、変数clearBtnに格納します。
// キャンバス(描画エリアの取得)
const canvas = document.querySelector('#drawing-area');
// 2dコンテキスト(描画ツールの取得)
const ctx = canvas.getContext('2d');
// 消去ボタン
const clearBtn = document.querySelector('#clear-button');

変数を宣言する

次に行うのは、変数の宣言です。

  • キャンバスのどこに描くかを決めるのは、x 座標と y 座標です。描画の開始位置を指定するための変数 xy を用意します。
  • mousePressed は、マウスボタンが押されている true か、押されていない false かを示す変数です。この変数の値によって、描画するかしないかを切り替えます。最初は、マウスボタンが押されていない false で初期化します。
// 描画開始点 横の位置
let x;
// 描画開始点 縦の位置
let y;
// マウスボタンが押されているかどうか(trueまたはfalse)
let mousePressed = false;

マウスボタンを押して描画を開始する

要素の取得と変数の用意ができたら、マウスボタンを押して描画を開始するためのコードを書いていきます。

マウスボタン(タッチパッドなど)を押したときに発生するのは、mousedown イベントです。addEventListener() メソッドでキャンバスに mousedown イベントを追加し、startDrawing を呼び出しましょう。startDrawing は描画を開始する関数で、このあと定義します。

  • キャンバス内でマウスボタンを押したとき、描画を開始します。
// キャンバスにmousedownイベントを追加
canvas.addEventListener('mousedown', startDrawing);

マウスなどを押したときに発生するイベントといえば、click イベントを思いつくかもしれませんね。click イベントはマウスが押されて離された後に発生するのに対し、mousedown は押下した時点でイベントが発生するという違いがあるので、覚えておきましょう。

描画を開始する関数

ここからは、キャンバス内でマウスボタンを押したときに実行する関数 startDrawing を定義していきます。

startDrawing は、マウスボタンが押されたところから描画を開始できるように、押された位置の座標を取得する関数です。関数には、startDrawing(e) のように引数 e を指定します。こうすることで、キャンバス内で mousedown イベントが発生したときに、その座標情報を受け取ることができるようになりますよ。

// 描画を開始する
function startDrawing(e) {

  // マウスボタンが押されていることを示す
  // 押下された位置を取得する

}

イベントオブジェクトを表す e について、詳しくは以下の記事で解説しているのでご覧ください。

JavaScript - イベントオブジェクト - function(e) の e とは?
function(e) の e はイベントオブジェクトを受け取るための変数です。イベントオブジェクトにアクセスして、押されたキーの種類やマウスポインターの座標などの情報を取得できるようになりましょう。

マウスボタンが押されていることを示す

描画を開始するためには、まず、マウスボタンが押されていることを示し、描画できる状態に切り替えます。

  • 変数 mousePressed の値を true にします。
function startDrawing(e) {

  // マウスボタンが押されていることを示す
  mousePressed = true;

  // 押下された位置を取得する
  // …
  
}

押下された位置を取得する

続いて、マウスボタンが押された x 座標と y 座標を取得し、それぞれ変数に格納しましょう。座標を取得するために使うのが、引数 e と、offsetXoffsetY プロパティです。引数 e には、mousedown イベントが発生したときの位置情報が保存されています。

  • e が持つ情報の中から、offsetX プロパティで x 座標(キャンバス内での横の位置) を取得します。
  • e が持つ情報の中から、 offsetY プロパティで y 座標(キャンバス内での縦の位置)を取得します。
function startDrawing(e) {
  mousePressed = true;

 // 押下された位置を取得する
  x = e.offsetX;
  y = e.offsetY;

}

以上で、マウスボタンが押された位置を変数 xy に格納し描画を開始する関数 startDrawing を定義できました。

マウスを動かしてキャンバスに描画する

次は、マウスを動かして描画するためのコードを書いていきます。

マウスなどを動かしたときに発生するのは、mousemove イベントです。キャンバスに mousemove イベントを追加して、draw を呼び出しましょう。draw は実際に描画するための関数で、このあと定義します。

  • キャンバス内でマウスを動かしたとき、描画します。
// キャンバスにmousemoveイベントを追加
canvas.addEventListener('mousemove', draw);

線を描画する関数

ここからは、キャンバスに線を描画するための関数 draw を定義していきます。この関数でもマウスの位置情報を利用するので、 draw(e) のように引数に e を指定してくださいね。

// 線を描画する
function draw(e) {

  // マウスボタンを押していないときは描画しない
  //*** 描画する処理 ***
  // 移動先の座標を取得する
  // 線を描画する
  // 開始点の座標を更新する

}

マウスボタンを押していないときは描画しない

キャンバスに描画するのは、マウスボタンを押している間だけです。まず最初に、マウスボタンが押下されているかどうかを確認しましょう。そのために使うのは、if 文です。もしマウスボタンを押していなければ、関数 draw は実行しないようにします。

  • 論理否定演算子 ! を使って、「マウスボタンを押していない」という条件を指定します。
  • return で、関数の実行を停止します。
function draw(e) {

  // マウスボタンを押していないときは描画しない
  if (!mousePressed) return;

  //*** 描画する処理 ***
  // 移動先の座標を取得する
  // 線を描画する
  // 開始点の座標を更新する
  // …
  
}

ここでは、「もしマウスボタンを押していないなら関数を実行しない」という false の場合の文を書きました。これは、下のように、「もしマウスボタンを押しているなら関数を実行する」という true の場合の if 文にすることもできますよ。

// trueの場合に関数を実行するコード例

function draw(e) {

  // もしマウスボタンを押しているなら
  if (mousePressed) {

    //*** 描画する処理 ***
    // …

  }
}

if 文での truefalse については、以下の記事で詳しく解説しているのでご覧ください。

JavaScript - 条件分岐の基本 - if 文の書き方と true, false の意味
if 文は、「もし〇〇ならAをする、そうじゃなかったらBをする」のように、条件によって処理を分岐させるための文です。if 文を理解する際に必要になる、真理値(真偽値)の true と false についても、一緒に学んでいきましょう。

移動先の座標を取得する

マウスボタンを押しているかいないかを if 文で条件分岐したら、線を描画する処理を書いていきましょう。

線を描くためには、どこからどこまで線を引くのか、その座標を指定する必要があります。開始点の座標は、先ほど定義した関数 startDrawing で変数 xy に格納されるようにしましたね。あと必要になるのは、移動先の座標です。

この関数 draw は、mousemove イベントが発生したときに呼び出されるので、引数 e にはマウスを動かした先の座標情報が含まれます。マウスの移動先座標を、それぞれ変数 x2y2 に格納しましょう。

  • e が持つ情報の中から、offsetX プロパティで x 座標を取得します。
  • e が持つ情報の中から、offsetY プロパティで y 座標を取得します。
function draw(e) {
  if (!mousePressed) return;

 // 移動先の座標を取得する
  x2 = e.offsetX;
  y2 = e.offsetY;

  // 線を描画する
  // 開始点の座標を更新する
  // …
  
}

線を描画する

開始点 xy、移動先 x2y2 の座標を使って、線を描きましょう。

  • beginPath() メソッドで、描画を開始します。
  • moveTo() メソッドで、どこから描き始めるのかを指定します。
  • lineTo() メソッドで、どこへ線を引くのかを指定します。
  • stroke() メソッドで、線を描画します。
function draw(e) {
  if (!mousePressed) return;

  x2 = e.offsetX;
  y2 = e.offsetY;

  // 線を描画する
  ctx.beginPath();    //描画開始
  ctx.moveTo(x, y);   //開始点
  ctx.lineTo(x2, y2); //移動先
  ctx.stroke();       //線を引く

  // 開始点の座標を更新する
  // …
  
}

beginPath() メソッドなどを使用して線を描く方法はこちらで解説しています。

開始点の座標を更新する

マウスを動かしながら線を描き続けるためには、開始点を移動先の座標で上書きしていく必要があります。mousemove イベントが発生するたびに、変数 x2y2 にはマウスの移動先の座標が入り、開始点 xy の値も更新することで、線が引かれます。

  • 変数 xy を、変数 x2y2 の値で更新する
function draw(e) {
  if (!mousePressed) return;

  x2 = e.offsetX;
  y2 = e.offsetY;

  ctx.beginPath();
  ctx.moveTo(x, y);
  ctx.lineTo(x2, y2);
  ctx.stroke();

  // 開始点の座標を更新する
  x = x2;
  y = y2;

}

以上で、キャンバスに線を描画するための関数 draw を定義できました。

マウスボタンを離して描画を終了する

さて今度は、マウスボタンを離したときに描画を終了するための文を書いていきます。この処理は、とても単純です。

マウスボタン(タッチパッドなど)の押下を離したときに発生するのは、mouseup イベントです。ウィンドウに mouseup イベントを追加して、描画できない状態に切り替えます。

  • 変数 mousePressed の値を false にします。
// ウィンドウにmouseupイベントを追加
window.addEventListener('mouseup', () => mousePressed = false);

ここでは、処理の文が1行のみなので、簡単にアロー関数で書いています。これは、たとえば関数にstopDrawingという名前をつけて宣言し、下のように書くこともできますよ。

// 関数を別で宣言するコード例

window.addEventListener('mouseup', stopDrawing);

function stopDrawing() {
  mousePressed = false;
}

注意:mouseup イベントはキャンバスではなくウィンドウに追加する

mouseup イベントは、mousedownmousemove とは異なり、canvas ではなく window に追加しました。これは、キャンバスの内側か外側かに関わらず、画面上でマウスボタンを離したときに必ず、描画できない状態 mousePressed = false にする必要があるからです。

もし下のように、canvasmouseup イベントを追加するとどうなるでしょうか。

// 間違いの例

canvas.addEventListener('mouseup', () => mousePressed = false);

上の文を使った場合、キャンバスの外でマウスボタンを離してからキャンバス内にポインターを戻すと、マウスを押下していないのに描画できてしまいます。理由は次のとおりです。

  • キャンバスの外で mouseup イベントが発生した場合には、mousePressed = false が実行されない。
  • つまり、押下をやめても mousePressed = true のままとなっている。
  • そのため、キャンバス内でポインターを動かすと関数 draw が実行されて描画できてしまう。

マウスボタンを離したときに確実に描画を終えるためには、「mouseup イベントは window に追加する」ということに注意してくださいね。

消去ボタンでキャンバスの内容を消去する

最後に、消去ボタンを押したときにキャンバスの内容を消すための文を書きましょう。ボタンを押したときに発生するのは click イベントです。キャンバスに描いたものを全て消去するためには、clearRect() メソッドでキャンバスの原点 (左上角) 0, 0 からキャンバスの幅 canvas.width と高さ canvas.height を指定します。

  • 消去ボタンに click イベントを追加し、clearRect() メソッドでキャンバス全体を消去します。
// 消去ボタンにclickイベントを追加
clearBtn.addEventListener('click', () => {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
});

完成コード

シンプルなお絵描きアプリのコードが完成です。

//*******************************
// シンプルなお絵描きアプリのコード
//*******************************

const canvas = document.querySelector('#drawing-area');
const ctx = canvas.getContext('2d');
const clearBtn = document.querySelector('#clear-button');

let x;
let y;
let mousePressed = false;

// 描画を開始する
function startDrawing(e) {
  mousePressed = true;
  x = e.offsetX;
  y = e.offsetY;
}

// 線を描画する
function draw(e) {
  if (!mousePressed) return;
  x2 = e.offsetX;
  y2 = e.offsetY;
  ctx.beginPath();
  ctx.moveTo(x, y);
  ctx.lineTo(x2, y2);
  ctx.stroke();
  x = x2;
  y = y2;
}

// マウス押下で描画開始
canvas.addEventListener('mousedown', startDrawing);

// マウスを動かして描画
canvas.addEventListener('mousemove', draw);

// マウスボタンを離して描画終了
window.addEventListener('mouseup', () => mousePressed = false);

/// 消去ボタンクリックで全消去
clearBtn.addEventListener('click', () => {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
});

まとめ

今回は、JavaScript を使ってキャンバスに自由に描画できる、シンプルなお絵描きアプリの作り方を紹介しました。

マウスボタンを押す mousedown、マウスを動かす mousemove、マウスボタンを離す mouseup、それぞれのイベントで実行する処理を分けることでマウスで線を描けるようにしました。描画する線の色や太さは、今回は特に設定していないので、デフォルトの黒 1px です。線の太さや色を選ぶ機能については、【機能追加編】の記事で紹介します。

最後まで読んでいただき、ありがとうございます。この記事をシェアしてくれると嬉しいです!

SNSで Pyxofy とつながりましょう! LinkedInThreadsMastodon X (Twitter) @pyxofyFacebook

関連記事

JavaScript - お絵描きアプリの作り方【機能追加編】
今回は、【基本編】で作ったシンプルなお絵描きアプリに機能を追加します。線の太さをスライダーで変更したり、色をカラーピッカーから選択できるようにして、より楽しく遊べるアプリを作ってみましょう。
CSS Art – How to Make a Game Character – Kirby
Pink all over and packed with powerful moves. Unforgettable starry eyes. Join us in creating the iconic Kirby in this article, step-by-step.
CSS Animation – Animate Along a Curved Path
Squares are so last century. What do rounded corners, smooth curves and roller coasters have in common. Find out in this step-by-step article.
スクラッチプログラミング - でんしゃをはしらせよう
線路(せんろ)にそって電車(でんしゃ)をはしらせましょう。電車(でんしゃ)にセンサーをとりつけて、線路(せんろ)からはみださないようにうごかします。いろで確認(かくにん)するセンサーなので、プログラムは簡単(かんたん)です。