該篇文章的例子同時也算是我CSS動畫的啓蒙之做,思路來源於掘友的一篇文章,文末有連接,但只提供思路,因而我打算實現一波,順便分享一下動畫製做過程當中的心得體會。也算是應證了,當你打開思路之後,動畫,真的很簡單。css
接上篇文章JS動畫?其實沒你想的那麼難。150行代碼,帶你走進新世界,本文繼續帶你們領略動畫之美。html
實例基於CSS3,暫未考慮瀏覽器兼容問題,更可能是幫助你們打開思路,實際作得很差的地方你們能夠本身修正,本身DIY。項目中的CSS是經過LESS
編譯的。如何在普通項目中使用LESS?,答案在LESS中文官網。只須要引入一個less.js
的CDN便可,是否是很方便?它會讓你愛上CSS。git
這是效果圖:github
下面咱們進入正題,教你們如何畫這兩種電池。canvas
想要玩好CSS動畫,障眼法是少不了的。何爲障眼法? api
這就是障眼法,這裏取消overflow:hidden;
便可看到原理,到這份上,也不用我多說了吧?下面講解關鍵代碼:
// 先畫個電池
<div id="battery1">
<div class="battery-anode"></div>
<div class="battery-body">
<div class="wave"></div>
<div class="wave" style="transform: rotate(90deg); opacity: 0.7;"></div>
<div class="wave" style="transform: rotate(45deg); opacity: 0.9;"></div>
<div class="charge"></div>
</div>
</div>
複製代碼
這裏,battery-anode
是正極,battery-body
是電池體,wave
是波浪,charge
是充電用的方形色塊。從圖中也能看出來,波浪是經過好幾塊方形旋轉製造出來的障眼法。這裏須要注意的是這些div的層疊關係,只有恰當的層疊才能顯現出合適的效果。瀏覽器
下面是動畫:bash
// 波浪的效果是主要是上下移動的過程當中一邊旋轉
@keyframes rotateAndUp {
0% {
margin-top: -50px;
}
100% {
margin-top: -215px;
transform: rotate(360deg);
}
}
// 充電動畫,設置顏色漸變,紅色到橙色到綠色,模擬充電狀態
@keyframes charge {
0% {
margin-top: 100px;
background-color: red;
}
50% {
margin-top: 25px;
background-color: orange;
}
100% {
margin-top: -50px;
background-color: limegreen;
}
}
複製代碼
相信你們在寫CSS的時候,看到@include,@keyframes,@media
等字眼時,都會本能的放棄思考,不要緊,我也是。老是以爲這些東西極其複雜。但實際上,仍是那句話,萬事開頭難,當你真的瞭解到它的強大以後,定會愛不釋手。app
動畫作完後須要添加到須要應用的dom元素上,使用css的animation
屬性便可。
animation: rotateAndUp 8s linear infinite;
複製代碼
設置infinite
動畫才能一直運動下去哦。
這裏有一點須要注意,@keyframes中,0%是動畫的起始狀態,其實就是動畫開始時元素將會當即轉變爲的狀態,若是幀動畫外定義了相同屬性則會被覆蓋。在上面的rotateAndUp
動畫中,你們可能想到:
爲何我要在DOM結構裏面單獨定義CSS?爲何幀動畫的0%裏面沒有加入transfrom: rotate()
?
首先,加入transform:rotate()
定義不一樣的初始狀態是爲了波浪錯位,這樣才錯落有致;其次,若是寫在0%裏面,那麼動畫只要一開始,相應的元素當即變爲0%中定義的狀態,相同屬性被覆蓋,一樣達不到效果,全部波浪將會保持一致。因此若是某些屬性是須要變換的,那麼直接寫在0%中便可,最多見的就是transform屬性了。另外這裏提到的是複雜動畫,常規動畫例如hover
效果,直接transform + transition
便可完美演繹。
細心的小夥伴能夠發現,CSS動畫雖然是循環執行的,可是每次結束時都是跳回原位重頭播放。有的時候咱們可能須要的效果並非這樣,也不是簡單的倒回去執行(reverse),而是平滑的過渡。若是你寫過@keyframes,不妨看看我說的對不對?
舉個例子,一個圓形360°旋轉並上下移動的動畫,通常來講很容易,但本身在控制幀動畫@keyframes的時候,老是達不到預期的效果,動畫末尾老是會跳幀,或者抖動,可想你的無奈。如何防止跳幀?這實際上是有技巧的,筆者屢次試驗總結髮現並不難,很簡單,想知道不?繼續往下看吧,就是接下來的,安卓充電動畫裏的上方大圓球。
這個動畫,第一眼看上去,你們最感興趣的應該就是這些小圓點如何移動,以及爲何他們能夠產生這樣粘稠的效果
首先,小圓球之間的粘稠效果是經過強大的CSS濾鏡:filter
,利用filter的contrast + blur
實現。使用blur模糊元素邊緣,再使用contrast增強對比度,便可實現。其中blur加在元素上,contrast加在背景上,背景必須有顏色,最好是可以和元素的顏色區分開,這樣才能更好的對比,實例中背景色爲白色。這裏舉個例子:如何看清一根頭髮?放到白紙上。
效果以下:
採用CSS,不難想到,咱們須要提早繪製好若干大小不一的圓點,在經過設置不一樣的時間,循環執行便可。此種方法須要繪製較多的圓形,須要許多DIV容器,DOM結構可能比較龐大,並且細心觀察是能夠看出動畫的規律的。爲了呼應上篇JS動畫的文章中提到的面向對象思想分解動畫,我決定採用JS實現。無規律的動畫纔有成就感,你說呢?
純CSS也能夠實現,有興趣的掘友能夠嘗試呀,兩種方法各有優劣。若是有結果的話,歡迎評論區留言,讓我看看嘛(手動賣萌)。
在上篇文章中提到,如何分解一個動畫?面向對象的思想分析可得,小球就是一個對象,咱們能夠抽象出一個類,用來繪製小球並動態插入。使用Math.random()計算隨機位置和半徑大小。經過Circle類描述圓點的屬性,經過Run類控制圓點的建立,插入,刪除,運動等。
這裏,順便推薦一個好用的,不依賴jQuery的動畫庫Velocity.js, 我比較喜歡他的一個特色,每個動畫均可以返回一個Promise對象,在某些狀況下涉及異步流程控制的時候,確實很好用,並且很輕量。
下面看DOM結構:
<!--電池容器-->
<div class="battery-container">
<!--上方旋轉的大圓-->
<div class="big-circle">98.7%</div>
<!--盛放小圓點的容器-->
<div class="circle-container">
// 隱藏層,主要幫助大圓實現融合濾鏡效果
<div class="decoration"></div>
<!--電池底座-->
<div class="bottom"></div>
</div>
</div>
複製代碼
爲了防止大圓的顯示受到對比度濾鏡的影響,單獨提出來寫,再定位到圓點容器上便可。因爲單獨提出來寫,致使大圓沒有融合效果,因此在圓點容器中增長一個隱藏層,定位到大圓下面,幫助它實現融合效果。這叫啥?沒錯,障眼法。
@radius等等是預先定義的變量,這就是LESS的方便之處,使用變量定義,修改時只需修改變量就能夠全局修改到全部引用了該變量的地方,拓展性很是好。
.big-circle {
width: @radius;
height: @radius;
line-height: @radius;
border-radius: @radius;
top: -@radius / 2;
font-size: 30px;
position: absolute;
z-index: 10; // 關鍵代碼,處於層疊上方
text-align: center;
background-color: white;
animation: breathe 3s linear infinite; // 關鍵代碼,呼吸動畫就是賦予圓球活力的感受
}
.circle-container {
width: 100%;
height: 100%;
position: relative;
background-color: white; // 關鍵代碼
filter: contrast(20); // 關鍵代碼,增強對比度
// decoration做用是幫助大圓實現融合效果,藏在大圓下面
.decoration {
width: @radius;
height: @radius;
border-radius: @radius;
position: absolute;
background-color: @limegreen;
filter: blur(6px);
animation: upAndDown 2s ease infinite; // 關鍵代碼
}
// 小圓球的樣式
.circles {
filter: blur(6px); // 關鍵代碼,模糊邊緣
background-color: @limegreen;
position: absolute;
bottom: 12px;
}
// 底座
.bottom {
width: @radius;
height: @radius / 3;
border-radius: 50% e('/') 100% 100% 0 0; // 畫橢圓的時候防止斜槓被LESS編譯成除法運算,因此這樣寫
position: absolute;
bottom: -1px;
background-color: @limegreen;
filter: blur(6px); // 關鍵代碼
}
}
複製代碼
上面賣的幀動畫防跳幀的關子如今告訴你們:
// 經過內外陰影變化模擬旋轉呼吸效果
@keyframes breathe {
from, to {
box-shadow: 3px 3px 20px @limegreen inset, -3px -3px @limegreen;
}
25% {
box-shadow: -3px 3px 20px @limegreen inset, 3px -3px @limegreen;
}
50% {
box-shadow: -3px -3px 20px @limegreen inset, 3px 3px @limegreen;
}
75% {
box-shadow: 3px -3px 20px @limegreen inset, 3px -3px @limegreen;
}
}
// .decoration的上下位移動畫
@keyframes upAndDown {
from, to {
top: -@scale/2 + 3px;
}
50% {
top: -@scale/2 + 8px;
}
}
複製代碼
其實很簡單,咱們只須要保證動畫首尾狀態相同,就能夠防止跳幀,平滑過渡。就是 from, to {} (或 0%, 100% {})
連在一塊兒寫。原理其實很簡單,動畫通過中間的變化最終又變了回去,既然變回去了,天然就是平滑的過渡了。
這裏咱們去掉from看看效果,去掉to也同樣:
很顯然,動畫進行到最後 回到起點時的過渡很是生硬,這不是咱們想要的效果。 我須要強調, 千萬不要小看了CSS,它就是這麼強,強的可怕,只要你能想到,就能作出來,問題是,大多時候咱們想不到,就拿障眼法來講,我也是剛學會。下面這個關於大圓我最初的想法,也從必定程度上也說明了CSS的強大。我最初的想法是:
.decoration
,在下面加一個圓可是很快我想起了CSS3新特性:box-shadow
,陰影效果能夠有多個,意思就是我能夠同時變換內外陰影,陰影就代替了我本來要加的這個隱藏圓,真的是方便。
當你準備在幀動畫中運用的時候,必定要畫個草圖研究一下參數哦。參數規劃的很差可能形成動畫每一個階段速度不一致,很奇怪的。拿最基礎的前兩個參數,也就是X軸和Y軸的偏移量來講。首先你要明白座標系是,向下向右爲正的。看似是旋轉動畫,實際上是分別變換左右和上下的陰影實現的障眼法,沒錯,又是障眼法。
經過瀏覽器中座標的規律咱們能夠推算出當內部陰影處在四個角落時候的座標以下圖所示,外陰影同理:
因此呀, 好的效果仍是要多嘗試,多打磨。但打磨也要講究方法,座標是規劃出來的,越複雜,越須要規劃,否則學數學幹嗎,不是隨便填兩個試試得來的。按照0% ~ 100%
中間四個幀,依次填充以上四個座標便可,無所謂前後,看你想要什麼樣子的效果了。我選的效果是內陰影順時針旋轉,因此個人座標填充順序是左上角開始,順時針。外陰影和內陰影相反,對調一組對角座標。
這就是我上面的座標由來,也是由於本身都快繞暈了,才索性找來紙筆好好規劃了一番。接下來說一講如何繪製這些小球。
負責描述圓點屬性,對應上篇文章中的Snowflake類。這個類仍是很簡單的。
class Circle {
constructor () {
this.init()
}
init () {
this.radius = random(15, 30)
this.vy = random(1500, 3000, true) // 使用時間描述速度,時間越長速度越慢
// x初始橫座標位置,須要控制在這個範圍內,可使得小球更加集中
this.x = random(Circle.range, Circle.width - Circle.range) // 這裏的
// 這就是主角啦
this.dom = this.create()
}
create () {
let c = document.createElement('div')
c.style.width = this.radius + 'px' // 寬高和邊框半徑相同便可畫出圓形來
c.style.height = this.radius + 'px'
c.style.borderRadius = this.radius + 'px'
c.style.left = this.x - this.radius / 2 + 'px' // 保證插入容器後是以中心點計算位置的,是否是很相似canvas
c.classList.add('circles') // 添加一個自定義類以便外部添加樣式
return c
}
}
複製代碼
爲何須要init()
方法來初始化屬性,直接寫在constructor中不是更好?
不是的,動畫涉及的對象屬性是須要按期更新的,有了這個方法,類的實例才能經過調用原型中的init()方法重置全部屬性,這樣才能達到隨機效果哦。
class Run {
constructor() {
this.container = document.querySelector('.circle-container')
// 設置靜態量,描述小球畫布範圍
Circle.width = this.container.offsetWidth // 畫布寬度
Circle.height = this.container.offsetHeight // 小球移動的最大高度
Circle.range = Circle.width / 4 // 左右留下的間距
this.circles = this.createCircle(10) // 造圓,數量可調整
this.run()
}
createCircle (num) {
let circles = new Set()
for (let i=0; i<num; i++) {
circles.add(new Circle())
}
return circles
}
animation (circle) {
this.container.appendChild(circle.dom)
// 引入velocity.js以後,dom元素將會掛載上velocity方法,直接使用便可,api同jQuery.animate()
return circle.dom.velocity({
bottom: Circle.height + 'px'
}, {
duration: circle.vy,
// 動畫完成後循環調用自身便可,挺像上篇文章裏的requestAnimationFrame的是否是?
complete: () => {
circle.init() // 刷新屬性
this.animation(circle)
}
})
}
run () { // 啓動動畫
this.circles.forEach(item => {
this.animation(item)
})
}
}
複製代碼
同樣的思路,同樣的面向對象,咱們又完成了一個動畫效果。也許往後咱們真的會用到本身作的這個動畫效果。有興趣的話能夠把它形成一個輪子,像上篇文章裏的snow.js同樣,封裝成一個小插件,留出可配置的接口,簡單配置便可完成動畫。
CSS中,不少看似高級的動畫,都是經過障眼法打造的。若是你仔細看完這篇文章,必定會有所收穫,就算沒看懂代碼,也能打開一些思路。若是能夠把GitHub中的代碼拷貝下來研究一下的話,相信你會學到更多。有興趣的掘友能夠爲倉庫貢獻一些代碼,或者留言區燥起來,都是很是歡迎的奧。
終於講完了,撰文一下午,腰痠背痛。若是你看完以爲有收穫,記得給個贊慰勞慰勞呀(卡姿蘭大眼睛),很是感謝。