ブログ

CSSのlinear-gradient()をアニメーションさせる「今どき」の方法

CSSのlinear-gradient()で表現するグラデーションをアニメーションさせる、さまざまなアプローチについて考察しています。よくある擬似要素を使用する方法に加え、カスタムプロパティを使ったアニメーション手法についても紹介しています。

よく知られている通り、linear-gradient()に指定した値にはトランジションの効果は及びません。

例えば以下の例では、ボタンにホバーした際、linear-gradient()で指定している色を反転し、transitionでbackgroundを対象にしていますが、アニメーションは適用されません。

アニメーションしない例
style.css
/* ボタン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)」であり、変化前後の型同士の値が補完されないためです。そこでグラデーションをアニメーションをさせるために生み出されたのが次の擬似要素を使用する方法です。

擬似要素でアニメーションさせる方法

以下が実装例です。ホバーするとゆっくりとグラデーションの色が反転することがわかります。

擬似要素でアニメーションする例
style.css
/* ボタン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の管理が意外と大変なケースもあります。そのような場合にはカスタムプロパティを使用することをオススメします。

さっそくですが以下がカスタムプロパティを使用した例です。

カスタムプロパティでアニメーションする例
style.css
/* ボタン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つのカラー変数を定義しています。

style.css
@property --_color-3 {
syntax: "<color>";
inherits: false;
initial-value: #13547a;
}
@property --_color-4 {
syntax: "<color>";
inherits: false;
initial-value: #80d0c7;
}

それぞれの型をcolorとして明示的に指定することで、その型に適した補間方法が自動的に設定されます。結果として、通常のCSSカスタムプロパティでは難しい値のアニメーションが可能となります。

カスタムプロパティにアニメーションを定義

上記で設定したカスタムプロパティの値に対してtransitionとマウスホバー時の変化を設定しています。

style.css
.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に直接カスタムプロパティを設定することはできません。

style.css
@property --my-color {
syntax: "<color>";
inherits: false;
initial-value: var(--another-color); /* これは動作しない */
}

つまり:rootや他の場所で定義したグローバルなカスタムプロパティ値をinitial-valueとして直接使用することはできないということです。これは@propertyの仕様による制限であり、登録時には具体的な初期値が必要とされているためです。

このため、グローバルな値を使用したアニメーションを実装する場合は、@propertyでカスタムプロパティ定義時には初期値を設定せず、必要に応じて通常のCSS内でグローバルな値を参照して上書きするというアプローチが必要になります。

style.css
: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); /* グローバルな値でカスタムプロパティを上書き */
}
}

以上、linear-gradient()をアニメーションさせる方法についてまとめました。

この手の話に正解はないと思うので、それぞれのプロジェクトにあった使い分けをしていこうかと思います。長々とお読みいただきありがとうございました。