CSSで作ったボタンの色を、マウスオンでふわっと切り替える。単色ボタンならtransitionを指定するだけで簡単に実現できますが、背景をグラデーションにした途端、この効果が効かなくなってしまいます。
結論から言うと、グラデーションボタンをふわっと変化させるには、擬似要素(::beforeと::after)に2つのグラデーションを重ね、opacityを切り替えるという方法を使います。この記事では、単色ボタンの基本から、なぜグラデーションだと効かないのか、そして擬似要素を使った解決法までを、動くデモとあわせて解説します。
まずは基本形です。単色の背景色であれば、transitionを指定するだけでマウスオン時にゆっくりと色が変化します。下のボタンにマウスを乗せてみてください。
<div class="btn btn1">
<a href="">
BUTTON 1
</a>
</div>
.btn a {
position: relative;
z-index: 0;
color: #fff;
width: 400px;
height: 80px;
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
text-decoration: none;
border-radius: 10px;
overflow: hidden;
transition: 0.5s;
}
.btn1 a {
background: rgba(0, 119, 255, 1);
}
.btn1 a:hover {
background: rgba(252, 69, 123, 1);
}
ところが、背景をグラデーションに変えると、同じtransitionを指定しても色がパッと切り替わるだけで、ふわっとした変化になりません。下のボタンで確認できます。
理由は、グラデーションの正体にあります。単色のbackground-colorはアニメーション可能なプロパティですが、linear-gradient()で作るグラデーションはbackground-imageとして扱われます。そしてbackground-imageは、多くのブラウザで滑らかに補間できないプロパティです。そのため、transitionを指定しても中間の状態が描画されず、瞬時に切り替わってしまうわけです。
補足すると、グラデーションの色だけを滑らかに変化させるアニメーションは、CSS単体では基本的に扱えません。単色で十分ならbackground-colorで組むのが最もシンプルですが、グラデーションの質感を保ったままふわっと変化させたい場合は、次に紹介する擬似要素を使う方法が定番の解決策になります。
<div class="btn btn2">
<a href="">
BUTTON 2
</a>
</div>
.btn a {
position: relative;
z-index: 0;
color: #fff;
width: 400px;
height: 80px;
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
text-decoration: none;
border-radius: 10px;
overflow: hidden;
transition: 0.5s;
}
.btn2 a {
background: rgb(0, 119, 255);
background: linear-gradient(90deg, rgba(0, 119, 255, 1) 0%, rgba(252, 69, 123, 1) 100%);
}
.btn2 a:hover {
background: rgb(255, 0, 0);
background: linear-gradient(90deg, rgba(255, 0, 0, 1) 0%, rgba(252, 176, 69, 1) 100%);
}
そこで発想を変えます。グラデーションそのものをアニメーションさせるのではなく、2枚のグラデーションを重ねて置き、上の1枚をopacityでフェードさせるのです。opacityはアニメーション可能なプロパティなので、これならふわっとした変化を実現できます。下のボタンが完成形です。
仕組みはこうです。::beforeに通常時のグラデーションを、::afterにマウスオン時のグラデーションを設定し、::beforeを手前(z-indexが上)に重ねておきます。通常は手前の::beforeだけが見えている状態です。
そしてマウスオン時に::beforeをopacity: 0;で透明にすると、奥に隠れていた::afterのグラデーションが透けて現れます。::before側にだけtransitionを効かせているため、透明になる過程がゆっくり進み、結果としてグラデーションがふわっと切り替わったように見える、というわけです。::afterは常に表示されたままなのでtransitionは不要で、動かすのは手前の::beforeだけ、という点を押さえるとシンプルに実装できます。
<div class="btn btn3">
<a href="">
BUTTON 3
</a>
</div>
.btn a {
position: relative;
z-index: 0;
color: #fff;
width: 400px;
height: 80px;
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
text-decoration: none;
border-radius: 10px;
overflow: hidden;
transition: 0.5s;
}
.btn3 a:before {
content: "";
width: 100%;
height: 100%;
position: absolute;
z-index: -1;
left: 0;
transition: 0.5s;
background: rgb(0, 119, 255);
background: linear-gradient(90deg, rgba(0, 119, 255, 1) 0%, rgba(252, 69, 123, 1) 100%);
}
.btn3 a:after {
content: "";
width: 100%;
height: 100%;
position: absolute;
z-index: -2;
left: 0;
background: rgb(255, 0, 0);
background: linear-gradient(90deg, rgba(255, 0, 0, 1) 0%, rgba(252, 176, 69, 1) 100%);
}
.btn3 a:hover:before {
opacity: 0;
}
擬似要素をボタンの背面に敷くため、親要素のaにはposition: relative;とoverflow: hidden;を指定しておくのがポイントです。角丸(border-radius)からグラデーションがはみ出さず、きれいに収まります。
変化のスピードは::beforeのtransitionの秒数(この例では0.5s)で調整できます。もっとゆっくり見せたいなら0.8sや1sに、キビキビ見せたいなら0.3s程度に変えてみてください。なおopacityは、ブラウザが効率よくアニメーションできるプロパティとして知られており、動作が軽く滑らかになりやすいという利点もあります。
細かい点ですが、デモのhrefにはサンプル用のダミーURLを入れています。実際のボタンではリンク先を差し替えてください。擬似要素はz-indexで背面(マイナス値)に置いているため、ボタンのテキストは常に手前に表示されます。もしテキストが見えない場合は、親要素のz-indexやpositionの指定を見直すと解決します。
transitionだけでふわっと色が変わるbackground-image扱いで補間できないため、transitionが効かない::beforeと::afterに2つのグラデーションを重ね、::beforeをopacityでフェードさせること「なぜ効かないのか」を理解しておくと、グラデーション以外の背景アニメーションでつまずいたときにも応用が利きます。擬似要素の重ね合わせは他の演出でも役立つテクニックなので、この機会に押さえておくと便利です。