Katashin .info

たった一つの変数でアニメーションのデバッグを簡単にする実装パターン

アニメーションを実装した時、そのアニメーションを観察してどこか違和感を感じた経験はありませんか?例えば、以下のデモは項目の追加時には高さが 0 から伸び、削除時には縮むアニメーションをしますが、これに違和感を感じる人もいるかもしれません。

このような場合、アニメーションの動きが速くて目立たない部分に、意図とは違う挙動、つまりバグが隠れている時があります。本記事では、こういったアニメーションのバグを見つけるデバッグの仕方や、デバッグを簡単にする実装パターンを紹介します。

アニメーションのデバッグ #

アニメーションのバグを見つけるには、その速度を落として観察すると良いです。以下のデモは一つ前のデモのアニメーション速度を十分の一に落としたものです。

速度を落としたアニメーションを観察すると、項目の追加、削除時に高さが完全な 0 になっておらず、padding の分だけ高さが残っていることがわかります。上下方向の padding も 0 にして、このアニメーションのバグを修正します。

アニメーションの速度を落とすにはコード内の duration の値を書きかえれば良いですが、複雑な実装になると、複数のアニメーションが組み合わさっている場合があり、一つ一つの duration を書きかえるのは大変です。次節では、この問題を解決する実装パターンを解説します。

アニメーションのデバッグを簡単にする実装パターン #

アニメーションのデバッグをより簡単にするために、あらかじめすべての duration 値に対して一つの変数をかけ合わせておきます。これにより、速度を変えたい時にはその変数の値を変えるだけで済みます。

変数 v を定義 #

JavaScript コード内に以下のような変数 v を定義します。v はデフォルト値 1 としておき、1 の時に通常の速度でアニメーションするようにします。

// この変数をすべてのアニメーションの duration にかける
const v = 1

JavaScript で変数 v を使う #

JavaScript でアニメーションを実装している部分には、すべての duration の値に変数 v をかけます。例えば、以下のように Web Animation API を使っているコードでは、第二引数の duration に変数 v をかけます。

document.getElementById('red').animate(
  // 右に 300px 移動するアニメーション
  [{ transform: 'translateX(0)' }, { transform: 'translateX(300px)' }],

  {
    // 意図しているアニメーションの時間 1000ms に対して変数 v をかける
    duration: 1000 * v,

    iterations: Infinity,
    easing: 'cubic-bezier(0.25, 1, 0.5, 1)',
  },
)
v = 1: Web Animation API を使ったアニメーションの通常版
v = 5: Web Animation API を使ったアニメーションの速度を落とした版

アニメーションに duration が無い場合は、かわりにフレームごとの経過時間を変換します。例えば、requestAnimationFrame を使っている場合は、前回のフレームからの経過時間を変数 v で割ります

const green = document.getElementById('green')
let rotation = 0
let lastTime

function loop(now) {
  if (lastTime) {
    // 前回のフレームからの経過時間を変数 v で割る
    const elapsed = (now - lastTime) / v

    // 回転するアニメーション
    // 経過時間から次の回転角度を計算する
    rotation = (rotation + (elapsed * 360) / 1000) % 360
    green.style.transform = `rotate(${rotation}deg)`
  }

  lastTime = now

  requestAnimationFrame(loop)
}

requestAnimationFrame(loop)
v = 1: requestAnimationFrame を使ったアニメーションの通常版
v = 5: requestAnimationFrame を使ったアニメーションの速度を落とした版

CSS で変数 v を使う #

変数 v を CSS でも使えるように、CSS カスタムプロパティ --v を定義します。

// setProperty で CSS カスタムプロパティ --v を定義
document.documentElement.style.setProperty('--v', v)

--v は calc 関数を使うことで他の数値とかけ合わせることができます。以下の例では animation-duration にカスタムプロパティ --v をかけています。

#blue {
  /* calc 関数を使って、1000ms の duration に --v をかける */
  animation-duration: calc(1000ms * var(--v));

  animation-name: slide;
  animation-timing-function: cubic-bezier(0.25, 1, 0.5, 1);
  animation-iteration-count: infinite;
}

@keyframes slide {
  to {
    transform: translateX(300px);
  }
}
v = 1: CSS Animation を使ったアニメーションの通常版
v = 5: CSS Animation を使ったアニメーションの速度を落とした版

変数 v の変更を UI ライブラリで検知させる #

これまでのコードで、デバッグ時にアニメーションの速度を変えるには、変数 v の値だけを変えれば良くなりました。しかし、コード上で変数 v の値を変えるたびに、ブラウザーをリロードしなければなりません。変数 v を UI 上で変更できるようにし、動的にアニメーションに適用できるようにすれば、さらに効率的にデバッグできます。

以下で変数 v と Vue.js を組み合わせて、動的にアニメーションの速度を変えられるようにしています。まず、変数 v を Vue.js の ref を使って定義し、変更を検知できるようにします。

import { ref } from 'vue'

const v = ref(1)

JavaScript で変数 v を使っていた部分では、v.value を参照するように書きかえます。

duration: 1000 * v.value,

CSS カスタムプロパティ --v の値は Vue.js の機能である、v-bind CSS 関数を使って定義することができます。

<style scoped>
/* アプリケーションのルートのコンポーネントで --v を定義 */
.root {
  /* v-bind 関数でコンポーネント内の値を CSS で使うことができる */
  --v: v-bind(v);
}
</style>

これで、変数 v を UI 上で変更した時に、その変更が動的にアニメーションの速度に反映されるようになります。以下は range スライダーによって変数 v の値を変え、アニメーションの速度を変えられるようにしたデモです。このようなデバッグ用の UI を開発環境で表示するようにしておくと、アニメーション実装の効率が上がります。

結論 #

アニメーションは自動テストを行うことが困難で、最終的には自分の目でチェックするしかありません。チェックやデバッグ作業を効率的にするために、実装の段階から工夫をしておくことは重要です。本記事で紹介したパターンは、duration の指定にひと手間かけるだけで、アニメーションのデバッグを簡単にできます。アニメーション実装の際の参考にしてみてください。