requestAnimationFrameで再帰っぽいロジックを使わずLoop構造を維持したままアニメーションを実現する（かつ速度調整も可能にする）


# サンプル
### 円を描く


<details open>
<summary>アニメーションを考慮せず、canvasに円を描くことを考えます</summary>

```javascript
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
const centerX = 150;
const centerY = 150;
const radius = 100;
const numPoints = 360; // ドットの数

function drawCircle() {
  // 描画する関数
  function draw(x, y) {
    ctx.beginPath();
    ctx.arc(x, y, 1, 0, 2 * Math.PI);
    ctx.fillStyle = 'red';
    ctx.fill();
    ctx.closePath();
  }

  for (let i = 0; i < numPoints; i++) {
    const angle = 2 * Math.PI * i / numPoints;
    const x = centerX + radius * Math.cos(angle);
    const y = centerY + radius * Math.sin(angle);

    draw(x, y);
  }
}

drawCircle();
```
</details>

---

### 円をアニメーションで表示

<details open><summary>ネットで検索したりAIに作成させたりすると、再帰っぽい感じでrequestAnimationFrame()関数で自分自身を呼び出すコードになります</summary>

```javascript
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
const centerX = 150;
const centerY = 150;
const radius = 100;
const numPoints = 360; // ドットの数
let currentPoint = 0;

function drawCircle() {
   // 描画する関数
  function draw(x, y) {
    ctx.beginPath();
    ctx.arc(x, y, 1, 0, 2 * Math.PI);
    ctx.fillStyle = 'red';
    ctx.fill();
    ctx.closePath();
  }

  if (currentPoint < numPoints) {
    const angle = 2 * Math.PI * currentPoint / numPoints;
    const x = centerX + radius * Math.cos(angle);
    const y = centerY + radius * Math.sin(angle);

    draw(x, y);
    currentPoint++;

    requestAnimationFrame(drawCircle);
  }
}

drawCircle();
```
</details>

---
<details open>
<summary>ここでプロミスを使うと元のLoop構造を残した形に出来ます</summary>

```javascript
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
const centerX = 150;
const centerY = 150;
const radius = 100;
const numPoints = 360; // ドットの数

async function drawCircle() {
  // 描画する関数
  function draw(x, y) {
    ctx.beginPath();
    ctx.arc(x, y, 1, 0, 2 * Math.PI);
    ctx.fillStyle = 'red';
    ctx.fill();
    ctx.closePath();
  }

  for (let i = 0; i < numPoints; i++) {
    const angle = 2 * Math.PI * i / numPoints;
    const x = centerX + radius * Math.cos(angle);
    const y = centerY + radius * Math.sin(angle);

    await new Promise(resolve => {
      requestAnimationFrame(() => {
        draw(x, y);
        resolve();
      });
    });
  }
}

drawCircle();
```
</details>

---

### アニメーション速度を調整可能にする
さらにもっと発展させて<FONT COLOR="RED">描画Speedをコントロール</FONT>できるようにします

そのためのAnimationDrawerクラスを考えました
使い方は、コード内のコメントおよびサンプルコードを参照ください
```javascript
//////////////////////////////////////////////////////////////////////////
/**
 * アニメーション付きで描画処理を行うためのクラス。
 * 1フレームあたりの描画回数を指定してアニメーションのSpeedを調整可能
 * @class AnimationDrawer
 * @example
 * const drawer = new AnimationDrawer(drawFunc, 2);
 * drawer.draw(x, y);
 * drawer.flush();
 */
//////////////////////////////////////////////////////////////////////////
class AnimationDrawer {
  // 1フレームあたりの描画回数（0の場合はアニメーションなし）
  #drawPerFrame;
  // 待機中の描画関数の引数をキューイングする配列
  #pendingArgs = [];
  // 描画関数
  #drawFunc;

  /**
   * コンストラクタ
   * @param {function} drawFunc 描画処理を行う関数
   * @param {number} drawPerFrame 1フレームあたりの描画回数
   *       （デフォルトは1、0の場合はアニメーションなし）
   */
  constructor(drawFunc, drawPerFrame = 1) {
    this.#drawFunc = drawFunc;
    this.#drawPerFrame = drawPerFrame;
  }

  /**
   * 描画命令をキューに追加
   * @param {...any} args - 描画関数の引数
   */
  draw(...args) {
    // drawPerFrameが0の場合はアニメーションなし
    if (this.#drawPerFrame === 0) {
      this.#drawFunc(...args);
      return Promise.resolve();
    }
    this.#pendingArgs.push(args);
    if (this.#pendingArgs.length >= this.#drawPerFrame) {
      return this.flush();
    }
    return Promise.resolve();
  }

  /**
   * キューにある描画命令を実行
   * @returns {Promise<void>}
   */
  flush() {
    if (this.#pendingArgs.length === 0) return Promise.resolve();
    return new Promise(resolve => {
      requestAnimationFrame(() => {
        for (const arg of this.#pendingArgs) {
          this.#drawFunc(...arg);
        }
        this.#pendingArgs.length = 0;
        resolve();
      });
    });
  }
}
```
<details open><summary>AnimationDrawerクラスを使ったサンプルコード</summary>

```javascript
const DRAW_PER_FRAME = 2; // 1フレームあたりの描画数（速度調整用）
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
const centerX = 150;
const centerY = 150;
const radius = 100;
const numPoints = 360; // ドットの数

async function drawCircle() {
  // 描画する関数
  function draw(x, y) {
    ctx.beginPath();
    ctx.arc(x, y, 1, 0, 2 * Math.PI);
    ctx.fillStyle = 'red';
    ctx.fill();
    ctx.closePath();
  }

  const animationDrawer = new AnimationDrawer(draw, DRAW_PER_FRAME);
  for (let i = 0; i < numPoints; i++) {
    const angle = 2 * Math.PI * i / numPoints;
    const x = centerX + radius * Math.cos(angle);
    const y = centerY + radius * Math.sin(angle);

    await animationDrawer.draw(x, y);
  }
  // DRAW_PER_FRAME引数を省略した（デフォルトの1）場合は不要
  await animationDrawer.flush();
}

drawCircle();
```
</details>
アニメーションする描画関数の上位関数にはすべて
<FONT COLOR="RED">await</FONT>をつける必要があります

---
ここでお試しが出来ます
<p class="codepen" data-height="400" data-default-tab="result" data-slug-hash="ZYGzddG" data-pen-title="アニメーションで円を描くサンプル" data-user="takanaweb5" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/takanaweb5/pen/ZYGzddG">
  アニメーションで円を描くサンプル</a> by takanaweb5 (<a href="https://codepen.io/takanaweb5">@takanaweb5</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://public.codepenassets.com/embed/index.js"></script>