Katashin .info

CSS 数式アニメーションで初速も考慮できる表現力の高いイージングを書く

cubic-bezier() をやめてこれからは数式で CSS アニメーションを書いていこうと思います。cubic-bezier() には簡単にアニメーションのイージングを書けるというメリットがありますが、凝ったことをしようとすると表現力が足りない問題に直面します。例えば、ユーザーが直前に行った操作に応じてアニメーションに初速をかけたい時、CSS アニメーションでやるのは難しいので、JavaScript で実装するというのが一般的です。

しかし、最近の CSS では、アニメーションさせたいプロパティに数式を記述することで、初速を考慮したイージングを実現できます。以下のデモは初速を考慮した CSS アニメーションの実装です。(記事執筆時点で Firefox に未実装の機能を使用しているので、Firefox 以外のブラウザーで見てください)

これを突き詰めると、スプリングアニメーション(Spring Animation)のような、従来は JavaScript でフレームごとに描画していたアニメーションも CSS アニメーションで実装できます。

iOS のスプリングを CSS 数式アニメーションで再現する

この記事では数式を使ったアニメーションを CSS で行う方法について、実際にデモを実装しながら解説します。

数式でアニメーションを表現できることの利点 #

数式でアニメーションを表現すると聞いて、ピンとこない人もいると思います。以下の画像はこの記事のはじめのデモで使っている数式をグラフにしたものです。このグラフの形がイージング関数に似ていると気づいた方もいるかもしれません。

初めのデモで使われている数式(初速 0)をグラフにしたもの

このようなグラフを描く数式を作ることができればイージングも自由に作れます。cubic-bezier() 関数も便利ですが、数式を直接記述する表現力には及びません。はじめのデモは初速を考慮したイージングを一つの式で表しています。上記のグラフは初速が 0 の時でしたが、以下のグラフは初速が +1000 px/s の時のグラフです。cubic-bezier() を使って初速を考慮したイージングを設定するのは難しいでしょう。

初めのデモで使われている数式(初速 +1000px / s)をグラフにしたもの

今まで数式を使ってアニメーションするには、JavaScript の requestAnimationFrame でフレームごとに数式の値を計算し、毎フレーム要素に反映させる必要がありました。最近の CSS の機能を使えば、数式を CSS アニメーション(もしくは CSS トランジション)させられます。

CSS 数式アニメーションの手順 #

では、数式で CSS アニメーションを行うにはどうすれば良いのでしょうか。以下の HTML と CSS をベースに、本記事はじめのデモのアニメーションを実装していきます。

<p>初速: 0 px/s</p>
<div class="ball red"></div>

<p>初速: +1000 px/s</p>
<div class="ball green"></div>

<p>初速: -500 px/s</p>
<div class="ball blue"></div>
body {
  margin: 10px auto;
  width: 350px;
}

.ball {
  width: 50px;
  height: 50px;
  border-radius: 50%;
}

.red {
  background-color: #c00;
}

.green {
  background-color: #0a0;
}

.blue {
  background-color: #00c;
}

@property でカスタムプロパティを CSS アニメーションへ適用可能にする #

まずは数式の中で使うカスタムプロパティの値に CSS アニメーションを適用できるようにします、@property ルールを使って、経過時間を表す --t カスタムプロパティを登録します。

@property --t {
  syntax: '<number>';
  inherits: false;
  initial-value: 0;
}

@property 内では以下の記述子でカスタムプロパティの設定を書けます。

syntax'<number>' を記述したことで、--t は数値として扱われるようになります。そして、数値なので CSS アニメーションを適用できるようになります。

登録したカスタムプロパティを使った数式を書く #

先ほど登録したカスタムプロパティ --t を使ってアニメーションさせたいプロパティ translate に数式を書きましょう。ここでは数式の解説は詳しく行いませんが、先の節で紹介したグラフのように、初速(ここでは --v)を考慮したイージングを描くと考えてください。

.ball {
  /*
   * 初速を考慮したスプリングっぽいアニメーション。
   * (デモ用にかなり適当に書いてます)
   * --v は初速を表すカスタムプロパティ。
   * --t は経過時間を表すカスタムプロパティで 0 から 1 へと変化する。
   */
  /* prettier-ignore */
  translate: calc(
    -300px
      * (
        (var(--v) / -150 * var(--t) + 1)
        / (1 + 700 * var(--t) * var(--t) * var(--t) * var(--t))
      )
    + 300px
  );
}

登録したカスタムプロパティに対して CSS アニメーションを適用する #

これでアニメーションの準備が整いました。--t を 0 から 1 へと変化させる @keyframes を定義し、アニメーションさせたい要素に適用します。@keyframes で変化させる値は translate ではなく、--t の方であることに注意します。また、イージングは数式で表現されているため、animation プロパティの timing-function は linear にします。

.ball {
  /*
   * --t を 0 から 1 へと変化させるアニメーションを2秒かけて繰り返す。
   * timing-function は linear にする必要があることに注意。
   */
  animation: time 2s linear infinite;
}

/*
 * --t を 0 から 1 へと変化させる。
 * translate ではなく --t の方を変化させる必要があることに注意。
 */
@keyframes time {
  from {
    --t: 0;
  }

  to {
    --t: 1;
  }
}

今回の式には初速の値 --v も含めているので、以下のように、各ボールに初速を設定します。

.red {
  --v: 0;
}

.green {
  --v: 1000;
}

.blue {
  --v: -500;
}

今までのコードを動かすと、以下のように書いた式通りに動くアニメーションになっています(再掲)。

CSS 数式アニメーションに関する仕様の実装状況 #

この節の内容は本記事執筆時点(2023年10月9日)のものです。最新の実装状況は適宜公式情報などで確認してください。

CSS 数式アニメーションを行うには @property もしくは CSS.registerProperty でカスタムプロパティを登録する必要がありますが、この仕様は Firefox の安定版では実装されていません。現状、Nightly 版での実装が確認されているので、近いうちに安定版でも使えるようになるでしょう。

また、本記事のデモでは --t の4乗を表すために var(--t) を4回かけていましたが、これは Chromium 系ブラウザーに pow() 関数が実装されていないためです。pow() 関数の他にも exp()sqrt() など、複雑な数式を実現するための CSS 関数が Chromium 系ブラウザーでは実装されていません。CSS 数式アニメーションを実装する際にはこれらの対応状況に注意する必要があるでしょう。

結論 #

数式を組み立ててアニメーションに使うことは難しいですが、今までの cubic-bezier() 関数を使ったイージングよりも表現の幅が広がり、より自由なアニメーションを実装できるでしょう。本記事では、CSS 数式アニメーションを行うためのカスタムプロパティやアニメーションの設定の解説にとどめ、具体的にどのように数式を組み立てれば良いかの解説は見送りました。「iOS のスプリングを CSS 数式アニメーションで再現する」では iOS で使われているスプリングアニメーションの数式を CSS で実装する方法を解説しています。数式でイージングを作る基本的な考え方を学びたい方は、そちらの記事も参考にしてみてください。