【入門者向け】Canvas入門講座#10 円をボタンを押して動かそう【JavaScript】

#問題10

中心が(150, 150)、半径50の円を塗りつぶしなさい。
塗りつぶす色はマゼンタ色(#ff00ff)であること。
rightボタン押下時に円がX方向へ-30,leftボタン押下時に円がX方向へ+30移動すること。
なお、以下のHTMLを使うこと。

```html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>問題10</title>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
$(() => {    
    // ここにプログラムを書く
});
</script>
</head>
<body>
<canvas id="my-canvas" width="500" height="300"></canvas>
<br>
<input id="left-button" type="button" value="right" />
<input id="right-button" type="button" value="left" />
</body>
</html>
```

#答え

```html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>問題10</title>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
$(() => {

    let moveX = 0;

    // 初期表示
    drawCircle(moveX);
    
    $('#left-button, #right-button').on('click', e => {
        if($(e.target).prop('id') === 'left-button') {// left-button がクリックされた
            moveX -= 30;
        } else {// right-button がクリックされた
            moveX += 30;
        }
        drawCircle(moveX);
    });

    function drawCircle(moveX) {
        // コンテキストを取得
        const ctx = $('#my-canvas')[0].getContext('2d');

        // 現在の描画状態を保存する
        ctx.save();
        
        // canvasをクリア
        ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);

        // 塗りつぶしの色をマゼンタ色にする
        ctx.fillStyle = '#ff00ff';

        ctx.beginPath();        // 現在のパスをリセットする
        ctx.arc(150 + moveX, 150, 50, 0, Math.PI*2, true); // 円を描画する
        ctx.closePath();        // パスを閉じる
        ctx.fill();           // 現在のパスを塗りつぶす

        // 描画状態を保存した時点のものに戻す
        ctx.restore();
    }
});
</script>
</head>
<body>
<canvas id="my-canvas" width="500" height="300"></canvas>
<br>
<input id="left-button" type="button" value="left" />
<input id="right-button" type="button" value="right" />
</body>
</html>
```

円が移動しました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163605/c2c74131-3a6b-d6a4-cc4e-cf07e1eb6742.png)

#解説
円がどれだけ動いたか変数で保持します。

```js
let moveX = 0;
```

まず円を描画します。今回は円の描画を関数にしてあります。

```js
// 初期表示
drawCircle(moveX);
```

ボタンクリックをまとめてハンドリングします。
left-button がクリックされたら、moveXを-30します。
right-button がクリックされたら、moveXを+30します。

```js
$('#left-button, #right-button').on('click', e => {
    if($(e.target).prop('id') === 'left-button') {// left-button がクリックされた
        moveX -= 30;
    } else {// right-button がクリックされた
        moveX += 30;
    }
    drawCircle(moveX);
});
```

drawCircleは円を描画する関数です。
引数はmoveXでX方向にどれだけ移動したかを受け取ります。

canvasへ描画する前にclearRectメソッドでcanvasをクリアしています。
これはクリアしないと1つ前の円の描画そのまま残るのを防ぐためです。
clearRectの代わりにfillRectによる塗りつぶしでも構いません。

<b>一般的にcanvasに何かを描画するときは、canvasをクリアした後に、canvasへ描画します。</b>

あと、描画する前に<b>save</b>メソッドを、描画が完了した後には<b>restore</b>メソッドを呼ぶようにしたほうが良いです。
saveメソッドは描画情報(塗りつぶしや線の色など)を保存し、restoreメソッドはそれを復元します。

```js
function drawCircle(moveX) {
    // コンテキストを取得
    const ctx = $('#my-canvas')[0].getContext('2d');

    // 現在の描画状態を保存する
    ctx.save();

    // canvasをクリア
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);

    // 塗りつぶしの色をマゼンタ色にする
    ctx.fillStyle = '#ff00ff';

    ctx.beginPath();        // 現在のパスをリセットする
    ctx.arc(150 + moveX, 150, 50, 0, Math.PI*2, true); // 円を描画する
    ctx.closePath();        // パスを閉じる
    ctx.fill();           // 現在のパスを塗りつぶす

    // 描画状態を保存した時点のものに戻す
    ctx.restore();
}
```

#補足

<b>setTransform</b>を使うと変数を描画処理で参照しなくてよいのですっきりします。
今回の問題は描画が単純ですので、このような工夫は必要ありませんが
setTransformは知っててほしいメソッドです。

setTransformは行列を掛けるメソッドです。
平行移動する場合はctx.setTranform(1, 0, 0, 0, x, y);のように呼び出しましょう。

今回のCanvas入門講座ではオブジェクトの変換は平行移動しかしないので、
setTransformの代わりにtranslateでもよいです。

setTranformについて詳しく知りたい方は<a href="https://qiita.com/PG0721/items/bbff0250ae25a324fbce">こちらの記事</a>をお読みください。

以下、drawCircleをsetTransformを使って書き直しました。

```js
function drawCircle(moveX) {
    // コンテキストを取得
    const ctx = $('#my-canvas')[0].getContext('2d');

    // 現在の描画状態を保存する
    ctx.save();
        
    // canvasをクリア
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);

    // 塗りつぶしの色をマゼンタ色にする
    ctx.fillStyle = '#ff00ff';

    ctx.setTransform(1, 0, 0, 1, moveX, 0);

    ctx.beginPath();        // 現在のパスをリセットする
    ctx.arc(150, 150, 50, 0, Math.PI*2, true); // 円を描画する
    ctx.closePath();        // パスを閉じる
    ctx.fill();           // 現在のパスを塗りつぶす

    // 描画状態を保存した時点のものに戻す
    ctx.restore();
}
```