【入門者向け】Canvas入門講座#11 円をタイマーで動かそう【JavaScript】

#問題11

中心が(150, 150)、半径50の円を塗りつぶしなさい。
塗りつぶす色はマゼンタ色(#ff00ff)であること。
円を最初右側へ動かし、canvasの右端に到達したら、左側へ動かしなさい。
左端に到達したら、右側へ動かしなさい。
タイマー処理はrequestAnimationFrameで行うこと。
なお、以下のHTMLを使うこと。

```html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>問題11</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>
</body>
</html>
```

円がタイマーで移動しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163605/966f69ef-ddc9-3306-9b75-e003cb50dea2.png)


#答え

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

    const RADIUS = 50;  // 円の半径
    let posX = 150, // 円の中心のX座標
        deltaX = 2; // 円の中心のX方向の移動量

    // アニメーション開始
    anim();

    function anim() {
        posX += deltaX;
        if(posX - RADIUS <= 0) {// 円がcanvasの左端に到達した
            deltaX *= -1;
        } else if(posX + RADIUS >= 500) {// 円がcanvasの右端へ到達した
            deltaX *= -1;
        }
        drawCircle(posX);
        requestAnimationFrame(anim);
    }

    function drawCircle(posX) {
        // コンテキストを取得
        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, posX, 0);

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

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

#解説
円がの半径、円の中心のX座標、
円の中心のX方向の移動量(右側へ動いているときは+2、左側へ動いているときは-2)を定義します。

```js
const RADIUS = 50;  // 円の半径
    let posX = 150, // 円の中心のX座標
        deltaX = 2; // 円の中心のX方向の移動量
```

アニメーションをタイマーで行う時は
setTimeoutやsetIntervalより<b>requestAnimationFrame</b>が推奨されています。
使い方はsetTimeoutと似ています。
最初にanimメソッドを呼んでいますが、animメソッドは円の位置を変更し、描画を行います。
animメソッドの最後でrequestAnimtionFrameを呼び、animメソッド自信を渡しています。
こうすることによって、一定時間(通常は1/60秒)毎にanimメソッドが呼ばれます。

```js
// アニメーション開始
anim();

function anim() {
    posX += deltaX;
    if(posX - RADIUS <= 0) {// 円がcanvasの左端に到達した
        deltaX *= -1;
    } else if(posX + RADIUS >= 500) {// 円がcanvasの右端へ到達した
        deltaX *= -1;
    }
    drawCircle(posX);
    requestAnimationFrame(anim);
}
```

円の描画は問題10と対して変わりません。

```js
function drawCircle(posX) {
    // コンテキストを取得
    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, posX, 0);

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

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