@propertyを用いてCSSアニメーションを作成する

## はじめに
この記事では[`@property`](https://drafts.css-houdini.org/css-properties-values-api/#at-property-rule)を用いたアニメーションの作り方を紹介します。これを利用することで、JavaScriptを用いて構築せざるを得なかった多くのアニメーションをCSSだけで構築できます。

:::note warn
2023年7月7日現在[can i use](https://caniuse.com/?search=%40property)によると、`@property`はすべてのモダンブラウザで対応されていません。
JavaScriptでは以下のようにして対応の可否を確認できます。
```js
typeof window.CSS.registerProperty !== 'undefined'
```
`@propery`はプロパティではなくルールであるため、CSSを用いた判定は少し大変です。
[web.devの`@property`の記事](https://web.dev/at-property/)によれば以下のようにして判定を行います。
```css
/* Check for Houdini support & register property */
@supports (background: paint(something)) {
  @property --gradPoint {
    syntax: '<percentage>';
    inherits: false;
    initial-value: 40%;
  }
}
```
`@property`を直接的に検査するのではなく、`paint`ワークレットを用いて擬似的に検査しています。

この機能を用いてアニメーションを作る場合は、`@property`が実装されていないブラウザで得られる情報に差が生まないような実装にしてください(アニメーションから得られる体験は除きます)。
:::

## `@property`とは
`@property`はCSSのアットルールの1つです。アットルールはCSSの動作を規定するCSSの記法の1つで、`@keyfrmames`や`@import`などが有名です。
`@property`はCSS変数(CSSカスタムプロパティとも呼ばれますが、この記事ではCSS変数と呼びます)を型のチェックなどの強い制約を課して定義します。
具体的には型と初期値の設定、値の継承についての定義を行います。
```css
@property --chart-percentage {
  syntax: '<percentage>';
  inherits: false;
  initial-value: 30%;
}
```
`@property`の後ろに変数名、`syntax`でその型、`inherits`で継承の有無、`initial-value`で初期値を定義します。

`syntax`に与えられる型は14個あります。主観ですがよく使われる型を紹介します。
- `'<length>'`: `1px`や`1rem`など`width`プロパティに渡すような値
- `'<number>'`: 整数や少数
- `'<percentage>'`: 整数や少数の末尾に％がついた値
- `'<color>'`: カラーコードとして取り得る値
- `'<integer>'`: 整数
- `'<angle>'`: `30deg`や`3.14rad`のような角度を表す値

このように定義した型に違反する値でCSS変数を上書きを試みても、更新されずに元の値を持つCSS変数として扱われます。

`inherits`は`true`と`false`で継承の有無を指定します。

`initial-value`はCSS変数の初期値を定義します。`syntax`で定義した型通りに値を渡す必要があり、一部の初期値を持つ型は省略可能です。

このように`@property`によって定義されたCSS変数は通常のCSS変数が持たない1つの特徴を持ちます。それはトランジションで変化させることが可能ということです。単純なCSS変数とは異なり、`@property`で定義された変数はこのような機能を持つので議題となっているアニメーションで活用できると言う訳です(`@property`についての説明で本題を忘れそうでしたね)。

このCSSのルールはJavaScriptからも定義できます。ブラウザに定義された`window`の`CSS`インターフェイスが持つ`registerProperty`メソッドです。
記法は`@property`とほとんど同じです。

```js
window.CSS.registerProperty({
  name: "--chart-percentage",
  syntax: "<percentage>",
  inherits: false,
  initialValue: "30%",
});
```
CSS変数名の定義が`name`で行われること以外は同じように情報を定義します。JavaScriptから定義したい場合はこちらを利用してください。

## アニメーションを作る
こちらのHTML/CSSによって作られたチャートに`@property`によって定義されたCSS変数を用いたアニメーションを付与していきます。
<p class="codepen" data-height="300" data-default-tab="css,result" data-slug-hash="RwqZYeG" data-user="kokisakano" style="height: 300px; 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/kokisakano/pen/RwqZYeG">
  normal-chart</a> by KokiSakano (<a href="https://codepen.io/kokisakano">@kokisakano</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>
ここでは徐々に青の領域が増えていくようなアニメーションを作ります。青の領域は`conic-gradient`で指定する%の値に依存して変化します(90%の部分です)。そのためその領域をCSS変数化して0%から徐々に増やしていく方針で実装します。


まずは`@property`で変数の定義を行います。

```css
@supports (background: paint(something)) {
  @property --chart-percentage {
    syntax: '<percentage>';
    inherits: false;
    initial-value: 0%;
  }
}
```
型は`'<percentage>'`に、継承は考慮したくないので`false`、初期値は`0%`にしました。

次に、このCSS変数を利用してチャートを表示させます。
```css
.main {
  --chart-percentage: 90%;
  background: conic-gradient(blue 0% var(--chart-percentage), gray var(--chart-percentage) 100%);
}
```
`conic-gradient`の`90%`を`var(--chart-percentage)`に置き換えて、`--chart-percentage`の値も90%に更新させました。

ここまでの変更は見た目も動作も全く変化がありませんでした。最後にアニメーションを付与します。
先に、キーフレームを定義します。`@property`で定義されたCSS変数ではないとトランジションできないので`@supports`内に書きます。ここでは同じ場所で定義しますが、`@property`で変数を定義する時に変化後の値を指定できない場合などでは別の箇所で定義して問題ないです。

```css
@supports (background: paint(something)) {
  @property --chart-percentage {
    syntax: '<percentage>';
    inherits: false;
    initial-value: 0%;
  }
  @keyframes chart-percentage {
    from {
      --chart-percentage: 0%;
    }
    to {
      --chart-percentage: 90%;
    }
  }
}
```
0%から90%まで変化させるキーフレームが定義できました。キーフレームが準備で来たので`animation`を定義します。
```css
.main {
  --chart-percentage: 90%;
  animation: chart-percentage 0.5s;
  background: conic-gradient(blue 0% var(--chart-percentage), gray var(--chart-percentage) 100%);
}
```
キーフレームで定義した変化を0.5秒かけて行うようにしました。キーフレームは`@property`を使える時でしか利用できないようにしたので、非対応のブラウザではアニメーションが無効となり、アニメーションなしのチャートが表示されます。キーフレームを`@supports (background: paint(something))`外で行うと非対応のブラウザの表示に問題が発生するので注意してください。

ここまでの変更を加えるとチャートは以下のようになります。

<p class="codepen" data-height="300" data-default-tab="css,result" data-slug-hash="WNYEgWJ" data-user="kokisakano" style="height: 300px; 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/kokisakano/pen/WNYEgWJ">
  animation-chart</a> by KokiSakano (<a href="https://codepen.io/kokisakano">@kokisakano</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

このように`@property`を用いて定義したCSS変数を用いてその変化をキーフレームで表現できればアニメーションを簡単に作れます。これまでCSSだけでは定義できなかったさまざまなアニメーションをこれを用いることで表現できるのでとても便利です。

<p class="codepen" data-height="300" data-default-tab="css,result" data-slug-hash="MWzvPrg" data-user="kokisakano" style="height: 300px; 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/kokisakano/pen/MWzvPrg">
  animation-chart</a> by KokiSakano (<a href="https://codepen.io/kokisakano">@kokisakano</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

<p class="codepen" data-height="300" data-default-tab="css,result" data-slug-hash="ZEmJqdW" data-user="kokisakano" style="height: 300px; 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/kokisakano/pen/ZEmJqdW">
  animation-counter</a> by KokiSakano (<a href="https://codepen.io/kokisakano">@kokisakano</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>