示例代碼托管在: "http://www.github.com/dashnowords/blogs" 博客園地址: "《大史住在大前端》原創博文目錄" 華為雲社區地址: "【你要的前端打怪升級指南】" [TOC] 一. CSS動畫 和 JS動畫 Web動畫的本質是元素 狀態改變造成的樣式變更 ,CS ...
目錄
示例代碼托管在:http://www.github.com/dashnowords/blogs
博客園地址:《大史住在大前端》原創博文目錄
華為雲社區地址:【你要的前端打怪升級指南】
一. CSS動畫 和 JS動畫
Web動畫的本質是元素狀態改變造成的樣式變更,CSS動畫和JS動畫的區別並不是由語言來決定的,而是由兩者的特點和適用場景來判斷的。CSS
動畫簡潔高效,提升交互體驗而編寫的代碼可以輕鬆地和主要業務邏輯之間實現隔離,開發中建議優先使用;而當你需要更豐富的緩動函數,多對象關聯動畫或是需要在動畫執行的特定時間點關聯一些其他的業務邏輯等需要細節控制的場景中,JS
動畫就會顯得更加清晰且易維護,兩者從來都不是非黑即白的選項。
1.1 CSS動畫
CSS
動畫通常指使用transition
實現的過渡動畫和使用animation
來實現的關鍵幀動畫。
transition動畫
transition
動畫也被稱為“簡易補間動畫”,需要提供起始和結束兩個關鍵幀,瀏覽器才能夠完成樣式差異比對並計算出對應的過渡動畫。開發者編寫的CSS
代碼會在渲染之前被瀏覽器使用(也就是生成CSSOM
的過程),所以對於被渲染出來的元素而言,首屏渲染的結果就可以被當做是起始關鍵幀,那麼結束關鍵幀從哪裡來?首先通過JS
腳本來修改指定元素的樣式或是類名是可行的,另一種方式就是利用帶有交互事件屬性的CSS偽類
(例如:hover
或是:focus
),當對應的事件觸發時,新的樣式就會作用於指定元素,這種特性也可以理解為CSS
語法中的事件回調機制。當結束關鍵幀被創建後,瀏覽器就可以自動計算兩者之間的差異並完成過渡動畫。
transition
動畫的要點就是具有樣式差異的兩個關鍵幀。如果CSS
代碼中只包含一般的靜態選擇器(指CSS
代碼中不包含能夠造成HTML元素狀態變更的選擇器),那麼被渲染出的元素在整個生命周期中就只會擁有一個關鍵幀,也就是首次被渲染時的樣式,而1個關鍵幀或是2個沒有樣式差異的關鍵幀都無法進行插值計算,這也就不難理解為什麼首屏渲染時transition
不會生效了。
所以transition
動畫比較適合被用來實現指定元素在兩個明確的包含樣式差異的狀態之間往複切換的場景,像是滑鼠的移入移出,元素的聚焦失焦等。
animation動畫
animation
動畫需要使用@keyframes
關鍵詞先將動畫過程抽象出來,然後將其關聯給指定元素的animation
屬性,它可以看做是transition
動畫的加強版。
使用@keyframes
定義動畫時通常需要指定from
和to
兩個狀態(也可以使用0
和100%
),這意味著開發者只要按照語法要求去定義一個動畫過程,它至少會包含兩個關鍵幀,所以即使沒有CSS
偽類或JS
腳本的幫助,依然可以獨立實現動畫。如果沒有定義from
起始關鍵幀的樣式,animation
動畫也不會出錯,它會預設以指定元素在動畫開始時刻的樣式作為起始關鍵幀,並結合to
定義的結束關鍵幀和指定元素的animation
定製參數來完成補間動畫的計算,所以即使像下麵這樣的簡陋寫法在首屏渲染時依然可以生效:
<style>
.main{
height:100px;
width:100px;
animation:fadeIn 2s linear;
}
@keyframes fadeIn{
to{
background-color:yellowgreen;
}
}
</style>
<body>
<div class="main"></div>
</body>
其次,和transition
過渡動畫不同的是,animation
動畫在不存在樣式差異的關鍵幀之間也會執行動畫,附件的示例demo
中已經展示了上述幾種不同動畫實現方式,你可以使用Chrome DevTools
中的Animations
面板中來查看動畫的觸發效果:
最後,animation
動畫最顯著的特點就是起止狀態之間可以定義多個中間幀,這部分就不再贅述。綜上可知,animation
是一種強制執行的動畫,既對transition
過渡動畫失效的場景進行了補充實現,同時也增加了動畫細節的可定製性(例如迴圈動畫或往複動畫的實現),但它的功能擴展仍然是針對單過程動畫的。關於animation
動畫還不熟悉的讀者可以查看【MDN-CSS Animations】。
1.2 JS動畫
JS
動畫並不是指Web Animations API
(MDN文檔——Web Animations API ),它畢竟還只是個草案,瞭解一下即可。本節所說的JS
動畫,既包括在腳本中修改元素類名或動畫樣式的方式,也包括區別於【關鍵幀動畫】的另一種形式——【逐幀動畫】。逐幀動畫不再藉助瀏覽器內部的插值機制來生成渲染畫面,而是將對應的邏輯在JavaScript
中實現,每一幀的狀態都由JS
來計算生成,然後藉助requestAnimationFrame
來將動畫中的每一幀傳遞到渲染管線中,你可以使用任何自定義的時間函數來執行動畫,也可以同時方便地管理多個對象的多個不同動畫,另外動畫的進度也是全生命周期可感知的(CSS
動畫只有animationstart
和animationend
等少量的事件),你可以自由地實現動畫暫停或者恢復,又或者是在動畫執行到某一特定時刻時觸發其他的邏輯,很明顯,JS
動畫在細節控制能力、過程管理能力以及多對象管理能力上都要比純CSS動畫更強大,但隨之而來的複雜性也是必須要付出的代價,另一方面,JS
代碼運行在主線程之中,主線程的實時工況會對動畫的流暢度造成極大影響,而CSS
動畫則不必擔心。
以一個列表項的渲染動畫為例,通常都會採用階梯交錯動畫(也稱為stagger動畫
)來實現,階梯交錯動畫中,每一個元素執行的動畫實際上是一樣的,但是需要在前一個元素的動畫過程執行到特定時間點時自己才能開始執行動畫,後續的元素依次類推,就需要為每一個動畫執行項的animation
屬性設置遞增的delay
值,這樣的需求使用原生CSS
既難編寫也難維護,它通常需要藉助預編譯器才能夠實現,但是如果在JS
腳本中來完成相同的設定,相信大部分前端開發者都可以輕鬆做到。
1.3 小結
所以綜上可知,動畫的編寫姿勢,實際上就是在CSS
的簡潔性和JS
的細節控制力之間找到一個平衡點。CSS
動畫可以使用著名的animate.css
預設動畫庫,而JS
動畫可以藉助velocity.js
來實現,當然他們都不是唯一的選擇。
二. 使用Velocity.js實現動畫
velocity.js
是一個非常易用的輕量級動畫庫,它包含了jQuery
中$.animate( )
方法的全部功能,但是比jQuery
更流暢。velocity.js
的調用方式非常簡單,既支持全局函數的形式調用,也支持對象方法的形式調用,在源碼的主文件src/velocity.ts
中可以看到下麵的代碼:
if (window) {
const jQuery: {fn: any} = (window as any).jQuery,
Zepto: {fn: any} = (window as any).Zepto;
patchFn(window, true);
patchFn(Element && Element.prototype);
patchFn(NodeList && NodeList.prototype);
patchFn(HTMLCollection && HTMLCollection.prototype);
patchFn(jQuery, true);
patchFn(jQuery && jQuery.fn);
patchFn(Zepto, true);
patchFn(Zepto && Zepto.fn);
}
也就是說無論你使用原生JavaScript語法,還是項目中已經引用了jQuery
或Zepto
,都可以在返回的結果集上以對象方法的形式來調用velocity
函數(當然也可以用靜態方法的形式來調用),velocity
方法具有多個方法重載,一般形式為接收兩個參數,第一個參數是下一個關鍵幀的樣式,它和CSS中定義關鍵幀沒什麼本質區別,第二個參數是對動畫細節的定製,當多次調用velocity
對象方法時就可以實現多步驟動畫的效果,所以在適合的場景中下麵的調用都是合法的:
let element = document.querySelector('div');
//全局函數
Velocity(element, {width:200},{duration:2000});
//原生節點集合的對象方法調用
element.velocity({width:200},{duration:2000});
//jQuery或Zepto中的調用
$(element).velocity({width:200},{duration:2000});
$('div').velocity({width:200},{duration:2000});
//多步驟動畫
$('div')
.velocity({width:200},{duration:2000})
.velocity({height:100},{duration:2000})
.velocity({backgroundColor:'#3498db'},{duration:2000});
velocity.js
的V2
版本還處在beta
階段,API文檔需要在官方倉庫的wiki
中查看【velocity.js V2文檔】,它提供的主要擴展能力如下:
事件鉤子
熟悉現代
SPA
開發的小伙伴肯定不會對事件鉤子感到陌生,類組件中的生命周期鉤子就是這種形式,當用戶希望某些自定義方法可以在特定時刻運行時,就可以使用velocity
中的事件鉤子將自定義方法和動畫的執行關聯起來,很明顯,這種機制的存在增加了動畫的交互和感知性,開發者可以在各個感興趣的階段鉤入自己期望運行的函數。velocity.js
中提供的事件鉤子包括:begin
(在動畫開始時觸發),complete
(動畫結束時觸發),progress
(動畫過程中觸發),progress
鉤子每次執行時可以獲取到動畫執行情況的細節,例如元素的引用、完成進度的百分比、剩餘的時間以及和緩動函數有關的數據:element.velocity({ width:100 },{ begin:function(){/*...*/}, progress:function(elements, percentComplete, remaining, tweenValue, activeCall){}, complete:function(){/*...*/} });
動畫的編排和調控
velocity.js
可以很方便地對有約束關係的多個動畫進行管理和編排。例如通過配置queue:String
參數,就可以同時維護多個隊列,以便同時管理多個併發的順序執行隊列;配置stagger:Number
參數,就可以解決上一節中提到的階梯交錯動畫的場景;speed:Number
參數可以改變動畫執行的速度;loop
可以實現往返動畫;repeat
可以實現單向重覆動畫;例如前一節中提及的階梯交錯動畫就可以用下麵的代碼方便地實現:document.querySelectorAll('.box').velocity({marginLeft:500},{duration:5000,stagger:200});
velocity.js
中還可以用命令的方式直接控制動畫的執行,命令的使用格式方式為:element.velocity(COMMAND_STRING);
常用的命令字元串包括
pause
(暫停動畫),resume
(恢復暫停的動畫),stop
(停止動畫並保持當前狀態),finish
(結束動畫並應用結束狀態)以及用於註冊自定義命令、自定義緩動函數甚至自定義預設動畫等的registerXXX
命令。例如一段通過按鈕點擊來控制動畫暫停和播放的代碼:function bindControl(){ let flag = true; let dom = document.querySelector('#btn'); dom.addEventListener('click',function(){ dom.velocity(flag ? 'pause':'resume'); flag = !flag; }); }
集成預設動畫
如果你曾經使用過
animate.css
預設動畫庫,那麼恭喜你,在velocity
你依然可以用同樣的預設動畫名來實現動畫,使用時需要引入額外的補丁庫:<script src="./jquery.min.js"></script> <script src="./velocity.min.js"></script> <script src="./velocity.ui.min.js"></script>
預設動畫可以直接傳入關鍵詞來使用:
document.querySelector('.box').velocity('jello'); //也可以覆蓋預設的動畫參數 document.querySelector('.box').velocity('jello',{ duration:2000 });
如果對各種動畫形式還不熟悉,可以直接在【Animate.css官方網站】上直接查看預設動畫的效果。不難看出,純CSS
動畫面臨的問題在JavaScript
的幫助下基本都得到瞭解決。下一篇中將分析瀏覽器高性能動畫的實現,敬請期待。