数字のカウントアップを良い感じのアニメーションで実装したい

インフォグラフィックなどで、数字をカウントアップしたくなったときに役に立つかもしれないメモです。↓こんな感じにしたい。

![count.gif](https://qiita-image-store.s3.amazonaws.com/0/89103/a9814fd0-6876-632d-23cc-71f9008ea327.gif)

**サンプルに使っている共通のHTML**

```html
<div class="container">
  <p id="count" data-from="0" data-to="4096">0</p>
  <button id="button">Start!</button>
</div>
```

# setIntervalでカウントアップ
まずは素直に[setInterval](https://developer.mozilla.org/ja/docs/Web/API/Window/setInterval)で実装してみます。一応カウントアップはできていますが、なんだか無味乾燥な印象を受けますね。

```js
const DURATION = 3600
const el = {
  count: document.getElementById('count'),
  button: document.getElementById('button')
}

el.button.addEventListener('click', e => {
  const from = parseInt(el.count.dataset.from, 10)
  const to = parseInt(el.count.dataset.to, 10)
  const startTime = Date.now()

  const timer = setInterval(() => {
    const elapsedTime = Date.now() - startTime
    const progress = elapsedTime / DURATION
    
    if (progress < 1) {
      el.count.textContent = Math.floor(from + progress * (to - from))
    } else {
      el.count.textContent = to
      clearInterval(timer)
    }
  }, 16)
})
```

<p data-height="400" data-theme-id="0" data-slug-hash="EpBEvZ" data-default-tab="js,result" data-user="noplan1989" data-pen-title="CountUp Animation setInterval" class="codepen">See the Pen <a href="https://codepen.io/noplan1989/pen/EpBEvZ/">CountUp Animation setInterval</a> by noplan1989 (<a href="https://codepen.io/noplan1989">@noplan1989</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

# 動きに緩急をつける
setIntervalでは動きが単調なので緩急をつけていきます。

アニメーションに緩急をつけるには、Tween系のライブラリを使ってイージングを効かせるのが手っ取り早いと思うので、今回は[TweenMax](https://greensock.com/tweenmax)を選びました。

TweenMaxは、要素のプロパティだけでなく、通常のオブジェクトに対しても使えるので、数字のカウントアップに適用します。イージングのおかげで緩急がついて、カウントアップに血が通った気がしますね。

この例ではInOut系のイージングを選びましたが、ちゃちゃっと見せたいときはOut系のイージングの方がいいかもです。

```javascript
const DURATION = 3.6
const el = {
  count: document.getElementById('count'),
  button: document.getElementById('button')
}

el.button.addEventListener('click', e => {
  const from = parseInt(el.count.dataset.from, 10)
  const to = parseInt(el.count.dataset.to, 10)
  let obj = {count: from}
  
  TweenMax.to(obj, DURATION, {
    count: to,
    ease: Power3.easeInOut,
    onUpdate: () => {
      el.count.textContent = Math.floor(obj.count)
    }
  })
})
```

<p data-height="400" data-theme-id="0" data-slug-hash="XBLqXz" data-default-tab="js,result" data-user="noplan1989" data-pen-title="CountUp Animation TweenMax" class="codepen">See the Pen <a href="https://codepen.io/noplan1989/pen/XBLqXz/">CountUp Animation TweenMax</a> by noplan1989 (<a href="https://codepen.io/noplan1989">@noplan1989</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

# フォントを等幅にする
スピードに緩急はつきましたが、数字がカタカタしているのが気になるかもしれません。プロポーショナルフォントの場合、フォントの幅がそれぞれ違うので、どうしてもカタカタしてしまいます。

デザインが許すのであれば等幅のフォントに変更してしまいましょう。google fontsにも色々あります。
https://fonts.google.com/?category=Monospace

```html:index.html
<!-- Google Fontsから読み込み-->
<link href="https://fonts.googleapis.com/css?family=Roboto+Mono" rel="stylesheet">
```

```css:index.css
.count {
  font-family: 'Roboto Mono', monospace;
}
```

<p data-height="400" data-theme-id="0" data-slug-hash="RBzyyw" data-default-tab="js,result" data-user="noplan1989" data-pen-title="CountUp Animation monospace" class="codepen">See the Pen <a href="https://codepen.io/noplan1989/pen/RBzyyw/">CountUp Animation monospace</a> by noplan1989 (<a href="https://codepen.io/noplan1989">@noplan1989</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

だいたい良い感じになりました。
現場からは以上です。