CSSのlinear-gradient()をアニメーションさせる今どきの方法
CSSのlinear-gradient()関数のグラデーションをアニメーションさせる、さまざまなアプローチについて考察してみました。よくある擬似要素等で実装する方法に加え、カスタムプロパティを使った手法についても紹介しています。
よく知られている通り、linear-gradient()に指定した値にはトランジションの効果は及びません。
例えば以下の例では、ボタンにホバーした際、linear-gradient()で指定している色を反転し、transitionでbackgroundを対象にしていますが、アニメーションは適用されません。
/* ボタン1 */.button--01 { background: linear-gradient(to right, var(--color-1), var(--color-2)); transition: background .5s; /* これは機能しない */}
@media (any-hover: hover) { .button--01:hover { background: linear-gradient(to right, var(--color-2), var(--color-1)); }}これは、linear-gradient()で指定されるデータの型が「画像(image)」であり、変化前後の型同士の値が補間されないためです。そこでグラデーションをアニメーションさせるために生み出されたのが次の擬似要素を使った方法です。
擬似要素でアニメーション
以下が実装例です。ホバーするとゆっくりとグラデーションの色が反転(クロスフェード)することがわかります。
/* ボタン2 */.button--02 { position: relative; z-index: 1; background: linear-gradient(to right, var(--color-1), var(--color-2)); /* 親要素の背景 */}
.button--02::before { content: ""; position: absolute; inset: 0; z-index: -1; background: linear-gradient(to right, var(--color-2), var(--color-1)); /* 擬似要素の背景 */ opacity: 0; /* 初期状態は非表示 */ transition: opacity 0.5s;}
@media (any-hover: hover) { .button--02:hover::before { opacity: 1; /* ホバーで表示 */ }}仕組みはとてもシンプルで、①ボタンの背景と②ボタンの擬似要素の背景を用意し、②を初期状態では非表示に、ホバー時に表示へ切り替えています。これによりグラデーションがアニメーションしているように見せています。
| 初期状態 | ホバー時 | |
|---|---|---|
| ①親要素の背景 | 表示 | 表示 |
| ②擬似要素の背景 | 非表示 | 表示 |
ポイントとしては、親要素にz-index: 1;を、擬似要素にz-index: -1;を設定することです。
親要素にz-indexを指定することで新たにスタッキングコンテキストが生成され、擬似要素のz-index: -1がそのコンテキスト内で解決されるようになります。これにより、擬似要素が「親要素の背景より前面」かつ「親要素のコンテンツより背面」に配置される状態を作り出しています。
カスタムプロパティでアニメーション
多くの場合では前項の擬似要素を使用する方法で十分だと思います。ただ親要素のコンテンツが複雑だったりするとz-indexの管理が意外と大変なケースがあります。そのような場合にはカスタムプロパティを使用することをオススメします。
以下がカスタムプロパティを使用した例です。擬似要素の場合と同様にクロスフェードでグラデーションの色が変化します。
/* ボタン3 */@property --color-3 { syntax: "<color>"; inherits: false; initial-value: #13547a;}
@property --color-4 { syntax: "<color>"; inherits: false; initial-value: #80d0c7;}
.button--03 { background: linear-gradient(to right, var(--color-3), var(--color-4)); transition: --color-3 0.5s, --color-4 0.5s;}
@media (any-hover: hover) { .button--03:hover { --color-3: #80d0c7; --color-4: #13547a; }}最近見かけることが多くなってきた登録カスタムプロパティ@propertyを使用したアニメーションです。
擬似要素は不要となり要素の重なり順を配慮しなくて良いので、コードとしてはかなりスッキリしたものとなります。以下にポイントをまとめます。
カスタムプロパティとして色を定義
登録カスタムプロパティ@propertyを使用して2つのカラー変数を定義しています。
@property --color-3 { syntax: "<color>"; inherits: false; initial-value: #13547a;}
@property --color-4 { syntax: "<color>"; inherits: false; initial-value: #80d0c7;}それぞれの型を<color>として明示的に指定することで、その型に適した補間方法が自動的に設定されます。結果として、通常のCSSカスタムプロパティでは難しい値のアニメーションが可能となります。
カスタムプロパティにアニメーションを定義
上記で設定したカスタムプロパティの値に対してtransitionとマウスホバー時の変化を設定しています。
.button--03 { transition: --color-3 0.5s, --color-4 0.5s;}
@media (any-hover: hover) { .button--03:hover { --color-3: #80d0c7; --color-4: #13547a; }}transitionの値に直接カスタムプロパティを指定する書き方には違和感がありますが、@propertyで登録したカスタムプロパティでは有効な書き方です。
型が明示的に指定されているからこそ、ブラウザはこれらの値をアニメーション可能なものとして扱うことができます。
注意点
登録カスタムプロパティを使用する際に、次のようにinitial-valueに別のカスタムプロパティの値を設定することはできません。
@property --my-color { syntax: "<color>"; inherits: false; initial-value: var(--another-color); /* これは動作しない */}つまり:rootや他の場所で定義したグローバルなカスタムプロパティ値をinitial-valueとして直接使用することはできないということです。これは@propertyの仕様による制限であり、登録時には具体的な初期値が必要とされています。
グローバルなカスタムプロパティ値を使用するには、initial-valueにはフォールバック用のデフォルト値を設定しつつ、実際に使用する値はグローバルな値で上書きするという二重のアプローチが必要になります。
:root { --color-1: #13547a; --color-2: #80d0c7;}
@property --color-3 { syntax: "<color>"; inherits: false; initial-value: #13547a;}
@property --color-4 { syntax: "<color>"; inherits: false; initial-value: #80d0c7;}
.button--03 { --color-3: var(--color-1); /* グローバルな値で上書き */ --color-4: var(--color-2); /* グローバルな値で上書き */ background: linear-gradient(to right, var(--color-3), var(--color-4)); transition: --color-3 0.5s, --color-4 0.5s;}
@media (any-hover: hover) { .button--03:hover { --color-3: #80d0c7; --color-4: #13547a; --color-3: var(--color-2); /* グローバルな値で上書き */ --color-4: var(--color-1); /* グローバルな値で上書き */ }}実質的に登録カスタムプロパティのinitial-valueが意味を持たなくなり、コードの可読性が下がるのでおすすめはできない手法です。
なお、@propertyは2024年にすべての主要ブラウザで対応が完了しています。詳細なブラウザサポート状況はCan I Use をご確認ください。
メリットとデメリット
擬似要素を使用する方法と比較した場合のメリットとデメリットは以下の通りです。
| 方法 | メリット | デメリット |
|---|---|---|
| 擬似要素を使用 | 従来からある手法でコードの理解が容易 | 要素の重なり順を配慮する必要がある |
| カスタムプロパティを使用 | 要素の重なり順を配慮する必要がない | 登録カスタムプロパティを宣言する必要がある |
特に「要素の重なり順を配慮する必要がない」というのは大きなメリットだと感じています。後からアニメーションを追加したい場合などに、コードの修正が容易になることが期待できます。
background-positionでアニメーション
ここまでは補間によるクロスフェードの変化を紹介してきましたが、background-positionを使用するとグラデーションの横移動を表現することができます。
以下の例ではホバーするとグラデーションの色が横にスライドします。
/* ボタン4 */.button--04 { background: linear-gradient( 90deg, var(--color-1), var(--color-2) 50%, var(--color-1) ); /* グラデーションに3つの色を指定 */ background-size: 200% 100%; /* グラデーションの幅を2倍に */ background-position: 0 0; transition: background-position 0.3s;}
@media (any-hover: hover) { .button--04:hover { background-position: 100% 0; /* グラデーションの開始位置(x)を100%に移動 */ }}仕組みは単純でbackground-size: 200% 100%;でグラデーションの幅を2倍にし、background-position: 0 0;でグラデーションの開始位置を0%に設定しています。そして、ホバー時にbackground-position: 100% 0;に設定することで、グラデーションの開始位置(x)を100%に移動させています。
| 初期状態 | ホバー時 | |
|---|---|---|
| background-position | 0 0 | 100% 0 |
あらかじめlinear-gradient()関数で3つの色を指定することがポイントです。今回はvar(--color-1)、var(--color-2)、var(--color-1)の順に指定しています。
ふわっと色が切り替わる動きとは少し印象の異なるものとなり、よりグラデーションの動きを強調したい場合にこの方法がオススメです。
まとめ
以上、linear-gradient()をアニメーションさせる方法について解説しました。
今回紹介した3つの方法をまとめると次のようになります:
| 手法 | アニメーションの種類 | 視覚的印象 |
|---|---|---|
| 擬似要素 | クロスフェード(opacity補間) | ふわっと色が切り替わる |
| カスタムプロパティ | 色の補間(color interpolation) | ふわっと色が切り替わる |
| background-position | 空間移動(translation) | グラデーションが横にスライドする |
視覚的な違いや、メリット・デメリットを把握した上で、それぞれのプロジェクトやコンポーネントの要件に応じて使い分けるのがよいでしょう。
