たった一つの変数でアニメーションのデバッグを簡単にする実装パターン
アニメーションを実装した時、そのアニメーションを観察してどこか違和感を感じた経験はありませんか?例えば、以下のデモは項目の追加時には高さが 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 の指定にひと手間かけるだけで、アニメーションのデバッグを簡単にできます。アニメーション実装の際の参考にしてみてください。