CSS変数もtransitionしたい!

  • プロダクション
CSS変数もtransitionしたい!

目次

CSSカスタムプロパティ(通称CSS変数)、みなさんお使いでしょうか。筆者もCSS変数が便利すぎて中毒になっているコーダーのひとりですが、時おり不満をおぼえることもあります。

たとえば下のコードは、マウスホバーすると背景色が赤から青に変化する要素のCSSです。.example1transitionが効くのですが、.example2のほうは効きません。

/* transitionする */
.example1 {
    --color-1: #c00;
    --color-2: #00f;
    background-color: var(--color-1);
    transition: background-color .5s;
    &:hover {
        background-color: var(--color-2);
    }
}
/* transitionしない */
.example2 {
    --color: #c00;
    background-color: var(--color);
    transition: --color .5s;
    &:hover {
        --color: #00f;
    }
}

.example1ではふたつのCSS変数を用意してbackground-colorの値を:hoverで切り替えています。いっぽう、.example2CSS変数そのものを変化させてbackground-colorに影響を及ぼそうとしています。大差ないように見えますが、変数を参照するプロパティが増えてくると後者のほうが合理的に記述できます。

本稿執筆時点ではまだCSS変数そのものをtransitionanimationさせることはできません。手段はあるのですが、ブラウザの実装が追いついていないのが現状です。本稿では、その方法を少し先取りしてご紹介します。

その前に、なぜCSS変数は素直にtransitionできないのかをまずは考えていきましょう。

transitionできる値とできない値

JavaScriptのデータ型と同様に、CSSプロパティがとる値にも型があります。

CSS データ型 - CSS: カスケーディングスタイルシート | MDN

たとえばmarginやwidthといった寸法に関係するプロパティは、<length-percentage>(百分率を含む長さ)という型の値をとります。また、background-colorやborder-colorといった色に関係するプロパティは<color>という型の値をとります。これらのデータ型は、ふたつの値の間を計算で補間することが可能で、そのような値はtransitionさせることができます。

長さ20pxから80%へ、とか、#fffからrgb(0,0,255)へ、といったように、表現が違っても型さえ同じであれば補間は可能です。

widthの型は長さ、background-colorの型は色というように、CSSプロパティはそれぞれがとる値の型が厳密に定められていますから、ブラウザはこれに従って補間を行うことができます。

CSS変数がtransitionできないワケ

ところが、CSS変数はユーザーが名前と値のペアを定義しただけですから、その値がどんな「型」なのかブラウザには判断できません。--myValue: 0; とだけ定義しても、その0が果たして数値なのか長さなのか、はたまた角度なのかは誰にもわからず、どのように補間計算すればよいかもわかりません。CSS変数がtransitionできないのは、型がないからなのです。

銀の弾丸、@property

じゃあCSS変数に型を定義できるようにしちゃえ、というのが@propertyルールです。このように書きます。

@property --my-color {
    syntax: "<color>";
    inherits: true;
    initial-value: transparent;
}
@property --my-length {
    syntax: "<length-percentage>";
    inherits: false;
    initial-value: 100%;
}

カスタムプロパティに対して「値の型(syntax)」、「継承の有無(inherits)」、「初期値(initial-value)」を設定します。上記のようにすると、--my-color変数は「値に<色>をとるプロパティ」と認識されて色の補間が効くようになり、--my-length変数は「値に<長さ>をとるプロパティ」と認識されて寸法の補間が効くようになります。

@propertyルールはいまだブラウザの実装が道半ばですので、使用する際は現時点の対応状況をよく確認してください。

@property - CSS: カスケーディングスタイルシート | MDN

CSS at-rule: `@property` | Can I use... Support tables for HTML5, CSS3, etc

@propertyを使ってみよう

冒頭のボタンのコードにしかるべき@propertyルールだけを追記したのがこちらです。

/* transitionする */
.example1 {
    --color-1: #c00;
    --color-2: #00f;
    background-color: var(--color-1);
    transition: background-color .5s;
    &:hover {
        background-color: var(--color-2);
    }
}
/* transitionするはず */
.example2 {
    --color: #c00;
    background-color: var(--color);
    transition: --color .5s;
    &:hover {
        --color: #00f;
    }
}
/* @propertyルール */
@property --color {
    syntax: "<color>";
    inherits: true;
    initial-value: transparent;
}

@propertyに対応したブラウザでは無事に両方の要素がtransitionするようになりました。CSS変数--colorは<色>型の値をとるプロパティと認識されたのでtransitionが起こるようになり、--colorを参照しているbackground-colorもその経過をリアルタイムに受け取って、間接的に変化しています。

CSS変数という通り名が理解をさまたげがちですが、CSSカスタムプロパティはこのように「それを参照しているプロパティに変化が即時反映する」という見逃せない特徴があります。JavaScriptのような「再代入したら再計算」という手順が不要なのです。

色々なことができるようになる

CSS変数をtransitionさせることで、今までできなかった表現も可能になります。

背景グラデーション、待望のアニメ化

background-image: linear-gradient()といえばtransitionさせられないのが常識でした。background-image値の型は<image>で、補間の対象ではないからです。しかし、transitionするCSS変数を参照させれば、間接的にbackground-imagetransitionさせることができます。待望のアニメ化です。

下のサンプルは、マウスホバーするとカラフルなグラデーション背景が回転しながら白黒ツートンに変化するCSSです。linear-gradient()関数の引数をすべて変数化して、それぞれに@propertyルールを定義しています。@propertyに対応したブラウザであればアニメーションします。

.example {
    width: 600px;
    height: 300px;
    --color-a: hotpink;
    --color-b: aquamarine;
    --angle: 0deg;
    --percentage-a: 0%;
    --percentage-b: 100%;
    background-image: linear-gradient(var(--angle), var(--color-a) var(--percentage-a), var(--color-b) var(--percentage-b));
    transition-property: --color-a, --color-b, --angle, --percentage-a, --percentage-b;
    transition-duration: .5s;
    transition-timing-function: linear;
    &:hover {
        --color-a: black;
        --color-b: white;
        --angle: 360deg;
        --percentage-a: 50%;
        --percentage-b: 50%;
    }
}
@property --color-a {
    syntax: "<color>";
    inherits: false;
    initial-value: transparent;
}
@property --color-b {
    syntax: "<color>";
    inherits: false;
    initial-value: transparent;
}
@property --angle {
    syntax: "<angle>";
    inherits: false;
    initial-value: 0deg;
}
@property --percentage-a {
    syntax: "<percentage>";
    inherits: false;
    initial-value: 0%;
}
@property --percentage-b {
    syntax: "<percentage>";
    inherits: false;
    initial-value: 0%;
}

ひとつのCSS変数で全体を動かす

下の図は「スライダクランク機構」をCSSだけで再現したものです。@propertyに対応したブラウザであれば、回転運動を往復運動に変換する動作が確認できます。

実際にアニメーションしているのは@property<angle>型を定義したCSS変数ひとつだけで、画面上のパーツはすべてこの変数を参照し再計算を行うことで動いています。

@keyframes actuate {
    0% {
        --angleA: 0deg;
    }
    100% {
        --angleA: 360deg;
    }
}
@property --angleA {
    syntax: "<angle>";
    inherits: true;
    initial-value: 0deg;
}
:root {
    animation: actuate 2s linear infinite;
}

まとめ

ここまでご覧いただきました通り、CSS変数と@propertyを使うことで、従来JavaScriptなどを援用しないと実現できなかったような多彩で複雑な表現が可能になります。拙い解説でしたが、@propertyによって広がる夢を少しでも実感していただけましたら幸いです。

この記事の執筆者

G・I

コンサルティング事業部

この記事に関するご相談やご質問など、お気軽にお問い合わせください。

お問い合わせ

タグ一覧