【JavaScript】requestAnimationFrameを使って、一時停止・再生をできるようにする

## はじめに
現代のウェブ開発において、ユーザーエクスペリエンス（UX）は非常に重要な要素となっています。
そのため、多くのWEBサイトが滑らかなアニメーションによって、心地よい体験を提供していると思います。

ただ、アクセシビリティを意識して、開発するとアニメーションを一時停止・再生させる必要があります。

そのため、この記事では、`requestAnimationFrame` を使って、一時停止・再生させる方法を紹介します。

## 完成系
スタートボタンを押すと、カウントアップしていきます。
ストップボタンを押すと、カウントアップがストップします。
リセットボタンを押すと、カウントが０になります。

この記事では、このカウントアップの作り方を参考に一時停止・再生させる方法を紹介します。

<p class="codepen" data-height="400" data-default-tab="result" data-slug-hash="XWvvYXY" data-pen-title="requestAnimationFrame() - 一時停止・再生" data-editable="true" data-user="sp_degu" style="height: 400px; 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/sp_degu/pen/XWvvYXY">
  requestAnimationFrame() - 一時停止・再生</a> by でぐぅー | Qiita (<a href="https://codepen.io/sp_degu">@sp_degu</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>


## 作り方
### 01. HTMLを記載する
HTMLは以下のように記載します。

```sample.html
<div class="container">
  <p id="timer">0</p>
  <button id="button">スタート</button>
  <button id="reset">リセット</button>
</div>
```

### 02. CSSを記載する
CSSは以下のように記載します。

このスタイルは関係ないので、任意のスタイルを記載ください。

```sample.css
p {
  color: #FFFFFF;
  font-size: 24px;
  font-weight: 700;
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  width: 100%;
}

.container {
  display: grid;
  gap: 16px;
  width: 50%;
}

button {
  background-color: #62929E;
  border: none;
  border-radius: 8px;
  color: #FFFFFF;
  cursor: pointer;
  font-weight: 600;
  padding: 8px;
  &:hover {
    background-color: #324E54;
  }
}

body {
  align-items: center;
  background-color: #212529;
  display: flex;
  gap: 32px;
  height: calc(100vh - 40px);
  justify-content: center;
  margin: 0;
  padding: 20px;
  width: calc(100vw - 40px);
}
```

### 03. JavaScriptを記載する
#### ⚪︎ 一時停止・再生する仕組み

1. ページがloadされたら、`requestAnimationFrame`を発火させ、loop関数を動かす
1. 前のloop関数からの経過時間（`elapsed`）を算出する
1. タイマーがオンなら、表示している値（`counter`）に経過時間を足す
    - タイマーがオフなら、何もしない
1. 表示している値（`counter`）を 次のloop関数で使う値（`preCounter`）に保存する
1. 経過時間を算出するために `time` を `preTime` に保存する


#### ⚪︎ 実装

```
const timer = document.getElementById('timer');
const button = document.getElementById('button');
const reset = document.getElementById('reset');

var isStop = true; // カウントアップのON/OFFを管理
let preTime; // 前のloop関数のtimeを代入する変数
let preCounter = 0; // 前のloop関数で表示した値
let requestId;

const loop = (time) => {
  // 前のloop関数からの経過時間を算出
  const elapsed = preTime ? time - preTime : 0;
  
  if (!isStop) {
    // 前のloop関数で表示した値に経過時間を足している
    const counter = preCounter + elapsed * 0.001;
    timer.innerHTML = `${counter}`;
    
    // counterをpreCounterに保存する
    preCounter = counter;
  }

  // timeをpreTimeに保存する
  preTime = time;
  
  // 次のloop関数を発火
  requestAnimationFrame(loop);
}

window.addEventListener('load', () => {
  // loop関数を発火
  loop();
});

button.addEventListener("click", () => {
  isStop = !isStop;
  if (isStop) {
    button.innerHTML = "スタート";
  } else {
    button.innerHTML = "ストップ";
  }
});

reset.addEventListener("click", () => {
  preCounter = 0;
  timer.innerHTML = `${preCounter}`;
});
```

## まとめ
この記事では、`requestAnimationFrame` を使って、一時停止・再生させる方法を紹介します。

もし、Webサイトで `requestAnimationFrame`を使って、一時停止・再生させる必要がある場合は参考にしてみてください。

---

最後まで読んでくださってありがとうございます！

普段はデザインやフロントエンドを中心にQiitaに記事を投稿しているので、ぜひQiitaのフォローとX(Twitter)のフォローをお願いします。

https://twitter.com/degudegu2510