🦁

CSSアニメーションと変数

2021/05/14に公開

はじめに

CSS変数の使い方を知ることによってアニメーションで表現できる幅が広がるため、この記事を書こうと思った次第です。

完成品

今回題材にするのは6角形のサイズを変えるだけのシンプルなアニメーションです。

CSS変数

CSSにはCSSカスタムプロパティ(以下CSS変数)という機能が存在します。
カスタムプロパティという名前の通り、ユーザーは独自のプロパティを定義することができます。そして、そのプロパティの値を既存のプロパティに適用することができる変数のようなものです。

定義

CSS変数の定義方法は以下の通りです。

--variable-name: something_value;

ハイフン(--)2つを先頭につけたものがCSS変数として定義されます。
CSS変数に代入することのできる値は、色、単位、等の既存のプロパティに適用できる値であれば代入することができます。また、スペース区切りあるいはカンマ区切りにした複数の値を代入することもできます。

--mask: linear-gradient(red, red) border-box, linear-gradient(red, red) content-box;

アクセス

そして、変数へアクセスするにはvar()関数を使用します。

body {
    background: var(--variable-name);
}

スコープ

次に、CSS変数のスコープについてです。
スコープは定義した規則セット({・・・})内です。しかし、:root内で定義することによってCSSファイル全体で変数にアクセスすることができます。

/* 文書内全体で有効 */
:root {
  --primary-color: red;
  --fontsize-m: 16px;
  --fontsize-l: 20px;
  --rotate: 2turn;
  --gradient: linear-gradient(blue, red) content-box,
              linear-gradient(20deg, rgba(0, 255, 0, 1), yellow) border-box;
}

値の継承

CSS変数の値は継承されます。var()関数で呼び出されたCSS変数が存在しない時、親要素を参照します。

第2引数

次が本題なのですが、var()関数に第2引数があるのは知っていますか?
第2引数を使用することによって第1引数の変数が未定義だった場合、第2引数の値を適用します。平たく表現すると代替値で、SCSSの!defaultフラグのようなものです。
CSSアニメーションを作成していると疑似要素::before::afterのどちらにも同じような値を使用することがたびたびあります。
そのようなときに、var()関数の第2引数を使うとスマートに記述できます。

div::before,
div::after {
--deg: 10deg;
content: "";
display: block;
width: inherit;
height: inherit;
transform: rotate(var(--deg));
}

div::after {transform: rotate(calc(-1 * var(--deg)));}

これを

div::before,
div::after {
--deg: 10deg;
content: "";
display: block;
width: inherit;
height: inherit;
transform: rotate(var(--after-deg, var(--deg)));
}

div::after {--after-deg: calc(-1 * var(--deg));}

このように記述することができます。

解説

ここまでの知識を踏まえて6角形を動かすアニメーションを作成していきましょう。以下のコードを変数を使って書き換えていきます。

<div class="hex"></div>
CSS(変数の使用なしver.)
body {
  background: #333;
  display: grid;
  height: 100vh;
  margin: 0;
  place-items: center center;
}

.hex {
  width: 100px;
  height: calc(100px * 1.7320508);
  background: red;
  animation: scale 1s infinite alternate;
}

@keyframes scale {
  100% {
    width: 150px;
    height: calc(150px * 1.7320508);
  }
}

.hex::before,
.hex::after {
  content: "";
  position: absolute;
  display: inherit;
  width: inherit;
  height: inherit;
  background: inherit;
  transform: rotate(60deg);
}

.hex::after {
  transform: rotate(-60deg);
}

6角形の作成方針ですが、1:√3の長方形を3つ作り、そのうちの2つを±60°傾けることで完成します。数学的な証明は記事の本論から逸れてしまうため割愛いたします。
まず目に入るであろう、1.7320508という数字。こちらは√3を小数点第7位まで現した数字です。何も知らずに見た人は困惑すると思うので変数に置き換えましょう。また、縦横幅の両方で使用されている100pxも変数に置き換えます。

+:root {
+ --root3: 1.7320508;
+ --base: 100px;
+}

.hex {
- width: 100px;
- height: calc(100px * 1.7320508);
+ width: var(--base);
+ height: calc(var(--base) * var(--root3));
background: red;
  animation: scale 1s infinite alternate;
}

次に±60°傾ける部分を変数に置き換えます。
ここはCSS変数の第2引数の話をしたところと同じ書き方を使います。

.hex::before,
.hex::after {
+ --deg: 60deg;
  content: "";
  position: absolute;
  display: inherit;
  width: inherit;
  height: inherit;
  background: inherit;
- transform: rotate(60deg);
+ transform: rotate(var(--after-deg, var(--deg)));
}

.hex::after {
- transform: rotate(-60deg);
+ --after-deg: calc(-1 * var(--deg));
}

アニメーションの作成

6角形ができたので、アニメーションの作成に移りましょう。
しかし通常のCSS変数はアニメーション時に値を補間してくれないためスムーズなアニメーションを実装することができません。
そのためCSS Properties and Values API@propertyというat-rulesを使用した変数を使用します。

[ アニメーション/遷移 ]から参照されるカスタムプロパティの値は、その値が構文解析された型に則って,算出値により 補間される。

と書いてあるように、この定義方法では型を定義するため値の補間が可能となりアニメーションさせることができます。

構文は以下の通りです。

@property --変数名 {
  syntax: "<color>";
  initial-value: magenta;
  inherits: false;
}

一番目のsyntaxで変数の型を宣言します。
サポートされている型は以下の通りです。

二番目のinitial-valueは初期値です。syntaxで定義した型の初期値を定義します。
最後のinheritsは値の継承を行うかどうか判定します。値は真偽値(true, false)です。

それでは、こちらを使ってアニメーションさせていきましょう。

:root {
  --root3: 1.7320508;
- --base: 100px;
}
	
+@property --base {
+  syntax: "<length>";
+  initial-value: 100px;
+  inherits: true;
+}
@keyframes scale {
  100% {
-   width: 150px;
-   height: calc(150px * 1.7320508);
+   --base: 150px;
  }
}

これで完成です。

完成品

CSS(変数の使用ver.)
:root {
  --root3: 1.7320508;
}

body {
  background: #333;
  display: grid;
  height: 100vh;
  margin: 0;
  place-items: center center;
}

@property --base {
  syntax: "<length>";
  initial-value: 100px;
  inherits: false;
}

.hex {
  width: var(--base);
  height: calc(var(--base) * var(--root3));
  background: red;
  animation: scale 1s infinite alternate;
}

@keyframes scale {
  100% {--base: 150px}
}

.hex::before,
.hex::after {
  --deg: 60deg;
  content: "";
  position: absolute;
  display: inherit;
  width: inherit;
  height: inherit;
  background: inherit;
  transform: rotate(var(--after-deg, var(--deg)));
}

.hex::after {
  --after-deg: calc(-1 * var(--deg));
}

おわりに

よいCSSアニメーションライフを🎆🎆

注意

https://caniuse.com/?search=%40property
Safari等では対応していないため対応状況を確認したうえで使用してください。

Discussion