CSS 實(shí)現(xiàn)按鈕點(diǎn)擊動(dòng)效的套路
前端瓶子君,關(guān)注公眾號(hào)
回復(fù)算法,加入前端編程面試算法每日一題群
在 Web 中,大部分按鈕可能都是平平無(wú)奇的,有時(shí)候?yàn)榱藦?qiáng)調(diào)品牌特殊或者滿足特殊功能,可能需要給按鈕添加一點(diǎn)點(diǎn)擊動(dòng)效。比如,用過(guò) Ant Design 的小伙伴應(yīng)該都能發(fā)現(xiàn),在點(diǎn)擊按鈕的時(shí)候會(huì)有一個(gè)很微妙的水波動(dòng)畫(huà)

這就非常有特色了,看到這樣的按鈕自然會(huì)聯(lián)系上 Ant Design 。
動(dòng)畫(huà)過(guò)程其實(shí)不復(fù)雜,看了一下官方的實(shí)現(xiàn),是通過(guò) js 動(dòng)態(tài)更改屬性實(shí)現(xiàn)的,在點(diǎn)擊的時(shí)候,改變屬性,觸發(fā)動(dòng)畫(huà),當(dāng)動(dòng)畫(huà)結(jié)束之后,再將該屬性還原(還原是為了保證下次點(diǎn)擊仍然有動(dòng)畫(huà)),如下

看著好像有點(diǎn)麻煩?其實(shí),這種效果也是可以純 CSS 實(shí)現(xiàn)的,而且還能實(shí)現(xiàn)其他更多有趣的效果

一起看看吧~
一、CSS 過(guò)渡動(dòng)畫(huà)
通常 CSS 中實(shí)現(xiàn)動(dòng)畫(huà)有兩種思路,transition和animation。一般而言,簡(jiǎn)單的、需要主動(dòng)觸發(fā)(:hover 、:active或者動(dòng)態(tài)切換類名等)的可以用transition實(shí)現(xiàn),其他的都可以用animation。
回到這個(gè)例子,動(dòng)畫(huà)足夠簡(jiǎn)單了,就兩個(gè)變化,而且需要主動(dòng)觸發(fā)(這里是點(diǎn)擊,可以想到:active),所以優(yōu)先考慮用transition來(lái)實(shí)現(xiàn)。
觀察整個(gè)動(dòng)畫(huà),其實(shí)就是兩個(gè)效果疊加而成
陰影不斷擴(kuò)大 透明度不斷降低
那么,這個(gè)動(dòng)畫(huà)(過(guò)渡)的兩種狀態(tài)可以這樣來(lái)表示
/*?初始狀態(tài)?*/
button{
??opacity:?.4;
??transition:?.3s;
}
/*?擴(kuò)散狀態(tài)?*/
button{
??box-shadow:?0?0?0?6px?var(--primary-color);
??opacity:?0;
}
嗯,兩種狀態(tài)的樣式都寫(xiě)好了,怎么觸發(fā)點(diǎn)擊呢?
二、CSS 點(diǎn)擊動(dòng)畫(huà)
先完善一下基本樣式,假設(shè) HTML 結(jié)構(gòu)如下
簡(jiǎn)單美化一下
:root{
??--primary-color:?royalblue;
}
.button{
??padding:?5px?16px;
??color:?#000000d9;
??border:?1px?solid?#d9d9d9;
??background-color:?transparent;
??border-radius:?2px;
??line-height:?1.4;
??box-shadow:?0?2px?#00000004;
??cursor:?pointer;
??transition:?.3s;
}
.button:hover{
??color:?var(--primary-color);
??border-color:?currentColor;
}
然后添加陰影擴(kuò)散動(dòng)畫(huà),為了方便透明度的控制,這里用::after偽類單獨(dú)渲染
.button::after{
??content:?'';
??position:?absolute;
??inset:?0;
??border-radius:?inherit;
??opacity:?0.4;
??transition:?.3s;
}
如果按照正常的思路通過(guò):active來(lái)觸發(fā)過(guò)渡動(dòng)畫(huà),可能會(huì)這樣來(lái)實(shí)現(xiàn)
.button:active::after{
??box-shadow:?0?0?0?6px?var(--primary-color);
??opacity:?0;
}
效果如下:

嗯,好像不大對(duì)勁?接著往下看
三、CSS 過(guò)渡重置
為什么會(huì)有上面這種現(xiàn)象呢?這里提一下:active。:active只有在鼠標(biāo)按下時(shí)才會(huì)起作用,通常在點(diǎn)擊一個(gè)按鈕時(shí),都是輕輕地點(diǎn)擊,而不是長(zhǎng)按,如果在:active上添加動(dòng)畫(huà),那么在鼠標(biāo)抬起的時(shí)候,動(dòng)畫(huà)一般都沒(méi)有結(jié)束,所以會(huì)導(dǎo)致在鼠標(biāo)抬起的時(shí)候,動(dòng)畫(huà)馬上就停止了,如果是transition,還會(huì)有一個(gè)“回退”的過(guò)渡效果。
那么,有沒(méi)有什么方法可以只在鼠標(biāo)抬起的時(shí)候產(chǎn)生動(dòng)畫(huà)呢?
我的實(shí)現(xiàn)是這樣的,假設(shè)默認(rèn)就是有陰影(透明度為0)的狀態(tài),在:active的時(shí)候迅速去除陰影(這里的“迅速”,是指取消按下去的過(guò)渡動(dòng)畫(huà)),然后由于默認(rèn)是有過(guò)渡的,所以鼠標(biāo)抬起的時(shí)候陰影就回退到有陰影的狀態(tài)了,這樣可以保證按下去是沒(méi)有動(dòng)畫(huà)的,抬起來(lái)觸發(fā)過(guò)渡動(dòng)畫(huà)
整個(gè)流程其實(shí)是這樣:

取消過(guò)渡動(dòng)畫(huà)也很簡(jiǎn)單,設(shè)置時(shí)長(zhǎng)為 0 就行了,代碼實(shí)現(xiàn)就是這樣
.button::after{
??/*其他樣式*/
??opacity:?0;
??box-shadow:?0?0?0?6px?var(--primary-color);
??transition:?.3s;
}
/*點(diǎn)擊*/
.button:active::after{
??box-shadow:?none;
??opacity:?0.4;
??transition:?0s;?/*取消過(guò)渡*/
}
然后,神奇的效果就出來(lái)了!

這樣就實(shí)現(xiàn)了和 Ant Design 幾乎相同的點(diǎn)擊效果
四、其他動(dòng)效案例
上面其實(shí)提供了一種思路,只要是這種點(diǎn)擊動(dòng)畫(huà),都可以采用這種方式來(lái)實(shí)現(xiàn)。比如這樣一個(gè)刷新按鈕,需要點(diǎn)擊的時(shí)候轉(zhuǎn)一下

用這種思路就很容易了,這個(gè)例子比上面那個(gè)要簡(jiǎn)單一些,畢竟只有旋轉(zhuǎn)變化,沒(méi)有透明度變化,核心代碼如下
.icon{
????transform:?rotate(360deg);
????transition:?.5s;
}
.button:active?.icon{
????transform:?rotate(0);
????transition:?0s;
}
完整代碼可以訪問(wèn) ant design button (codepen.io),整合了更多的 demo

再比如這樣的點(diǎn)擊粒子動(dòng)效,原理也是相同的

在之前文章CSS實(shí)現(xiàn)一個(gè)粒子動(dòng)效的按鈕中已經(jīng)有講到,這里就不多說(shuō)了,完整代碼可以訪問(wèn) button-active (codepen.io)
五、更復(fù)雜的動(dòng)畫(huà)
前面提到過(guò),簡(jiǎn)單的動(dòng)畫(huà)可以用過(guò)渡transition來(lái)實(shí)現(xiàn),那么稍微復(fù)雜點(diǎn)的,比如下面這種 “Q彈Q彈” 的按鈕

這類動(dòng)畫(huà),單純的transition就無(wú)能為力了,必須借助animation來(lái)實(shí)現(xiàn),原理還是類似
先定義一個(gè)動(dòng)畫(huà)關(guān)鍵幀
@keyframes?tada?{
????from?{
????????transform:?scale3d(1,?1,?1)
????}
????10%,?20%?{
????????transform:?scale3d(.9,?.9,?.9)?rotate3d(0,?0,?1,?-3deg)
????}
????30%,?50%,?70%,?90%?{
????????transform:?scale3d(1.1,?1.1,?1.1)?rotate3d(0,?0,?1,?3deg)
????}
????40%,?60%,?80%?{
????????transform:?scale3d(1.1,?1.1,?1.1)?rotate3d(0,?0,?1,?-3deg)
????}
????to?{
????????transform:?scale3d(1,?1,?1)
????}
}
這個(gè)動(dòng)畫(huà)來(lái)自于 Animate.css 中的 tada,直接 copy 過(guò)來(lái)就行
然后讓按鈕動(dòng)起來(lái)
.button{
??animation:?tada?1s;
}
在點(diǎn)擊的時(shí)候重置動(dòng)畫(huà),直接重置動(dòng)畫(huà),animation可能會(huì)更好理解一些,這樣在抬起的時(shí)候會(huì)重新運(yùn)行動(dòng)畫(huà)
.button:active{
??animation:?none;
}
這樣就實(shí)現(xiàn)了,是不是出乎意料的容易?
不過(guò)有一點(diǎn)小瑕疵,每次頁(yè)面刷新,按鈕會(huì)主動(dòng)進(jìn)行一次動(dòng)畫(huà)(因?yàn)閯?dòng)畫(huà)是自動(dòng)執(zhí)行的),如下

那么,如何避免首次進(jìn)來(lái)時(shí)動(dòng)畫(huà)不執(zhí)行呢?
這里有一個(gè)小技巧,可以在默認(rèn)情況下設(shè)置動(dòng)畫(huà)時(shí)長(zhǎng)為 0 ,這樣在首次動(dòng)畫(huà)執(zhí)行后,馬上就結(jié)束了,然后在 hover時(shí)恢復(fù)默認(rèn)的動(dòng)畫(huà)時(shí)長(zhǎng),由于動(dòng)畫(huà)已經(jīng)結(jié)束,改變動(dòng)畫(huà)時(shí)長(zhǎng)也不會(huì)觸發(fā)動(dòng)畫(huà)再次運(yùn)行,所以實(shí)現(xiàn)就是
.button{
??animation:?jump?0s;
}
.button:hover{
??animation-duration:?1s;
}
.button:active{
??animation:?none;
}
這樣刷新頁(yè)面就不會(huì)再有動(dòng)畫(huà)了

接下來(lái),借助 animate.css 你可以更換任意的動(dòng)畫(huà),比如

完整代碼可以訪問(wèn)button-jump (codepen.io),整合了更多的 demo

六、總結(jié)和說(shuō)明
以上就是關(guān)于 CSS 點(diǎn)擊動(dòng)畫(huà)的幾個(gè)套路和一些案例,其實(shí)就是默認(rèn)執(zhí)行動(dòng)畫(huà),點(diǎn)擊時(shí)重置一下就行了。整體來(lái)說(shuō)代碼很簡(jiǎn)單,只是理解起來(lái)可能不是特別順暢,下面總結(jié)一下實(shí)現(xiàn)要點(diǎn):
簡(jiǎn)單動(dòng)畫(huà)用transition,其他用 animation transition 可以通過(guò)設(shè)置時(shí)長(zhǎng)為 0 來(lái)重置 animation 可以通過(guò)設(shè)置 none 來(lái)重置 在 :active 時(shí)重置動(dòng)畫(huà),點(diǎn)擊后會(huì)再次運(yùn)行動(dòng)畫(huà) 復(fù)雜的動(dòng)畫(huà)可以借助現(xiàn)有的動(dòng)畫(huà)庫(kù),例如 anmate.css 設(shè)置動(dòng)畫(huà)時(shí)長(zhǎng)為 0 可以避免首次渲染出現(xiàn)動(dòng)畫(huà)
相比 js 實(shí)現(xiàn),CSS 實(shí)現(xiàn)代碼更少,加載更快,無(wú)需等待 js 加載完成,體驗(yàn)更優(yōu)(比如天然支持敲空格鍵觸發(fā)),同時(shí)也更容易維護(hù)和使用,直接復(fù)制一個(gè)類名就行了。最后,如果覺(jué)得還不錯(cuò),對(duì)你有幫助的話,歡迎點(diǎn)贊、收藏、轉(zhuǎn)發(fā)???
關(guān)于本文
作者:XboxYan
https://segmentfault.com/a/1190000041400360
