【前端優化】動畫幾種實現方式總結和效能分析
動畫實現的幾種方式:效能排序
js< requestAnimationFrame <css3< Canvas
js實現方式:
1.setTimeout 自身呼叫 eg1
2.setInterval 呼叫 eg2
setTimeout的定時器值推薦最小使用16.7ms的原因(16.7 = 1000 / 60, 即每秒60幀)
為什麼倒計時動畫一定要用setTimeout而避免使用setInterval-------兩者區別及setTimeout引發的js執行緒討論
1.js執行緒討論
1.1 為什麼:單執行緒是JavaScript的一大特性。
JavaScript是瀏覽器用來與使用者進行互動、進行DOM操作的,這也使得了它必須是單執行緒這一特性。比如你去修改一個元素的DOM,同時又去刪除這個元素,那麼瀏覽器應該聽誰的?
1.2 js單執行緒工作機制是:當執行緒中沒有執行任何同步程式碼的前提下才會執行非同步程式碼
var t = true;
window.setTimeout(function (){
t = false;},1000);
while (t){}
alert('end')
JavaScript引擎是單執行緒執行的,瀏覽器只有一個執行緒在執行JavaScript程式
1.3 瀏覽器工作基本原理
一、瀏覽器的核心是多執行緒的,核心制控下保持同步,
至少實現三個常駐執行緒:javascript引擎執行緒,GUI渲染執行緒,瀏覽器事件觸發執行緒(http請求執行緒等)
- javascript引擎是基於事件驅動單執行緒執行的,JS引擎一直等待著任務佇列中任務的到來,然後加以處理,瀏覽器無論什麼時候都只有一個JS執行緒在執行JS程式。
- GUI渲染執行緒負責渲染瀏覽器介面,當介面需要重繪(Repaint)或由於某種操作引發迴流(reflow)時,該執行緒就會執行。
但需要注意 GUI渲染執行緒與JS引擎是互斥的,當JS引擎執行時GUI執行緒會被掛起,GUI更新會被儲存在一個佇列中等到JS引擎空閒時立即被執行。
- 事件觸發執行緒,當一個事件被觸發時該執行緒會把事件新增到待處理佇列的隊尾,等待JS引擎的處理。非同步事件:如setTimeOut、瀏覽器核心的其他執行緒如滑鼠點選、AJAX非同步請求等,(當執行緒中沒有執行任何同步程式碼的前提下才會執行非同步程式碼)
也就是說即使setTimeout為0,他也是等js引擎的程式碼執行完之後才會插入到js引擎執行緒的最後執行。
1.4 JavaScript中任務,一種是同步任務,一種是非同步任務。
同步任務:各個任務按照文件定義的順序一一推入"執行棧"中,當前一個任務執行完畢,才會開始執行下一個任務。
非同步任務:各個任務推入"任務佇列"中,只有在當前的所有同步任務執行完畢,才會將佇列中的任務"出隊"執行。(注:這裡的非同步任務並不一定是按照文件定義的順序推入佇列中)
//只有使用者觸發點選事件才會被推入佇列中(如果點選時間小於定時器指定的時間,則先於定時器推入,否則反之)
1.5 "任務佇列是什麼?非同步任務通常包括哪些?"
任務佇列(event loop):你可理解為用於存放事件的佇列,當執行一個非同步任務時,就相當於執行任務的回撥函式。
通常io(ajax獲取伺服器資料)、使用者/瀏覽器自執行事件(onclick、onload、onkeyup等等)以及定時器(setTimeout、setInterval)都可以算作非同步操作。
先來看一段程式碼來理解一下
console.log("1");
setTimeout(function(){
console.log("2");
},1000);
console.log("3");
setTimeout(function(){
console.log("4");
},0);
輸出結果: 1->3->4->2.
那麼在來看你這段程式碼。
var t = true;
window.setTimeout(function (){
t = false
},1000);
while (t){}
alert('end');
1.6 setTimeOut的討論
引數
描述
code
必需。要呼叫的函式後要執行的 JavaScript 程式碼串。
millisec
必需。在執行程式碼前需等待的毫秒數。
提示:setTimeout() 只執行 code 一次。如果要多次呼叫,請使用 setInterval() 或者讓 code 自身再次
原理:setTimeout呼叫的時候,JavaScript引擎會啟動定時器timer,當定時器時間到,就把該事件放到主事件佇列等待處理。
注意:瀏覽器JavaScript執行緒空閒的時候才會真正執行 ep3
millisec引數有什麼用?
那麼問題來了。setTimeout(handler,0)和setTimeout(handler,100)在單獨使用時,好像並沒有區別。(中間執行的程式碼處理時間超過100ms時)
millisec一般在多個setTimeout一起使用的時,需要區分哪個先加入到佇列的時候才有用,否則都可以設定成setTimeout(handler,0)
1.7 SetTimeout 與 setInterval的區別
setTimeout(function(){
/程式碼塊... /
setTimeout(arguments.callee, 10);
}, 10);
setInterval(function(){
/程式碼塊... /
}, 10);
setTimeout遞迴執行的程式碼必須是上一次執行完了並間格一定時間才再次執行
比仿說: setTimeout延遲時間為1秒執行, 要執行的程式碼需要2秒來執行,那這段程式碼上一次與下一次的執行時間為3秒. 而不是我們想象的每1秒執行一次.
setInterval是排隊執行的
比仿說: setInterval每次執行時間為1秒,而執行的程式碼需要2秒執行, 那它還是每次去執行這段程式碼, 上次還沒執行完的程式碼會排隊, 上一次執行完下一次的就立即執行, 這樣實際執行的間隔時間為2秒
這樣的話在我看來, 如果setInterval執行的程式碼時間長度比每次執行的間隔段的話,就沒有意義,並且隊伍越來越長,記憶體就被吃光了.如果某一次執行被卡住了,那程式就會被堵死
巨坑無比的setInterval
定時器的程式碼可能在程式碼還沒有執行完成再次被新增到佇列,結果導致迴圈內的判斷條件不準確,程式碼多執行幾次,之間沒有停頓。
JavaScript已經解決這個問題,當使用setInterval()時,僅當沒有該定時器的其他程式碼例項時才將定時器程式碼插入佇列。這樣確保了定時器程式碼加入到佇列的最小時間間隔為指定間隔
- 某些間隔會被跳過
2.多個定時器的程式碼執行之間的間隔可能比預期要小
大前端團隊 > 前端動畫實現 > image2017-11-28 14:24:25.png
5處,建立一個定時器
205處,新增一個定時器,但是onclick程式碼沒執行完成,等待
300處,onclick程式碼執行完畢,執行第一個定時器
405處,新增第二個定時器,但前一個定時器沒有執行完成,等待
605處,本來是要新增第三個定時器,但是此時發現,佇列中有了一個定時器,被跳過
等到第一個定時器程式碼執行完畢,馬上執行第二個定時器,所以間隔會比預期的小。
二 CSS3動畫
1.tansition
transition-property 要運動的樣式 (all || [attr] || none)
transition-duration 運動時間
transition-delay 延遲時間
transition-timing-function 運動形式
ease:(逐漸變慢)預設值
linear:(勻速)
ease-in:(加速)
ease-out:(減速)
ease-in-out:(先加速後減速)
cubic-bezier 貝塞爾曲線( x1, y1, x2, y2 )http://matthewlein.com/ceaser/
transition的完整寫法如下 img{ transition: 1s 1s height ease; }
單獨定義成各個屬性。 img{ transition-property: height; transition-duration: 1s; transition-delay: 1s; transition-timing-function: ease; }
/可以多個動畫同時運動 /用逗號隔開
transition:1s width,2s height,3s background;
/可以在動畫完成時間之後新增動畫延遲執行的時間 /
transition:1s width,2s 1s height,3s 3s background;
過渡完成事件
Webkit核心: obj.addEventListener('webkitTransitionEnd',function(){},false);
firefox: obj.addEventListener('transitionend',function(){},false);
/tansition動畫發生在樣式改變的時候 /
function addEnd(obj,fn) ---封裝適應與各個瀏覽器的動畫結束
{
//動畫執行完執行該函式 obj.addEventListener('WebkitTransitionEnd',fn,false); obj.addEventListener('transitionend',fn,false); //標準
}
addEnd(oBox,function(){
alert("end");
});
// 面臨兩個bug:1.tansition中有多個動畫時,每個執行完,都會有一個結束彈出
2.發生重複呼叫的情況--需要移除
//移除動畫執行完的操作
function removeEnd(obj,fn)
}
obj.removeEventListener('transitionend',fn,false); obj.removeEventListener('WebkitTransitionEnd',fn,false);
{
使用注意
(1)不是所有的CSS屬性都支援transition
http://oli.jp/2010/css-animat...
http://leaverou.github.io/ani...
(2)transition需要明確知道,開始狀態和結束狀態的具體數值,才能計算出中間狀態
transition的侷限
transition的優點在於簡單易用,但是它有幾個很大的侷限。
(1)transition需要事件觸發,所以沒法在網頁載入時自動發生。
(2)transition是一次性的,不能重複發生,除非一再觸發。
(3)transition只能定義開始狀態和結束狀態,不能定義中間狀態,也就是說只有兩個狀態。
(4)一條transition規則,只能定義一個屬性的變化,不能涉及多個屬性。
CSS Animation就是為了解決這些問題而提出的。
2.transform
rotate() 旋轉函式 取值度數 deg 度數 -origin 旋轉的基點
skew() 傾斜函式 取值度數
skewX()
skewY()
scale() 縮放函式 取值 正數、負數和小數
scaleX()
scaleY()
translate() 位移函式
translateX()
translateY()
Transform 執行順序問題 — 後寫先執行
-webkit-transform:rotate(360deg);
旋轉原點可以是關鍵字+畫素位置:相對於左上角作為零點:正為下,右
-webkit-transform-origin:right bottom;
-webkit-transform-origin:200px 200px;
一個transform可以有多個值:
-webkit-transform:rotate(360deg) scale(0.2);
-webkit-transform:skewX(45deg);
-webkit-transform:skewY(45deg);
-webkit-transform:skew(15deg,30deg);
3.Animation 關鍵幀——keyFrames
只需指明兩個狀態,之間的過程由計算機自動計算
關鍵幀的時間單位
數字:0%、25%、100%等
字元:from(0%)、to(100%)
格式
@keyframes 動畫名稱
{
動畫狀態
}
@keyframes miaov_test
{
from { background:red; }
to { background:green; }
}
可以只有to
必要屬性
animation-name 動畫名稱(關鍵幀名稱)
animation-duration 動畫持續時間
屬性:
animation-play-state 播放狀態( running 播放 和paused 暫停 )
animation-timing-function 動畫運動形式
linear 勻速。
ease 緩衝。
ease-in 由慢到快。
ease-out 由快到慢。
ease-in-out 由慢到快再到慢。
cubic-bezier(number, number, number, number): 特定的貝塞爾曲線型別,4個數值需在[0, 1]區間內
animation-delay 動畫延遲只是第一次
animation-iteration-count 重複次數/infinite為無限次
animation-direction 播放前重置/動畫是否重置後再開始播放
alternate 動畫直接從上一次停止的位置開始執行
normal 動畫第二次直接跳到0%的狀態開始執行
reverse
alternate-reverse
animation-fill-mode
forwards 讓動畫保持在結束狀態
none:預設值,回到動畫沒開始時的狀態。
backwards:讓動畫回到第一幀的狀態。
both: 根據animation-direction(見後)輪流應用forwards和backwards規則。
animation-play-state
paused
running
動畫播放過程中,會突然停止。這時,預設行為是跳回到動畫的開始狀態,想讓動畫保持突然終止時的狀態,就要使用animation-play-state屬性
大前端團隊 > 前端動畫實現 > image2017-11-28 14:29:8.png
animation也是一個簡寫形式
div:hover { animation: 1s 1s rainbow linear 3 forwards normal; }
分解成各個單獨的屬性
div:hover { animation-name: rainbow; animation-duration: 1s; animation-timing-function: linear; animation-delay: 1s; animation-fill-mode:forwards; animation-direction: normal; animation-iteration-count: 3; }
Animation與Js的結合
通過class,在class里加入animation的各種屬性
直接給元素加-webkit-animation-xxx樣式
animation的問題
寫起來麻煩
沒法動態改變目標點位置
animation的函式:
obj.addEventListener('webkitAnimationEnd', function (){}, false);
例項1:無縫滾動
animation的step
eg:http://dabblet.com/gist/1745856
animation-timing-function: steps(30, end)
1.什麼時候使用:
animation預設以ease方式過渡,它會在每個關鍵幀之間插入補間動畫,所以動畫效果是連貫性的,除了ease,linear、cubic-bezier之類的過渡函式都會為其插入補間。但有些效果不需要補間,只需要關鍵幀之間的跳躍,這時應該使用steps過渡方式
大前端團隊 > 前端動畫實現 > image2017-11-28 14:29:45.png
線性動畫:http://sandbox.runjs.cn/show/...
幀動畫:http://sandbox.runjs.cn/show/...
2.step使用:
語法:
steps(number[, end | start])
引數說明:
number引數指定了時間函式中的間隔數量(必須是正整數)
第二個引數是可選的,可設值:start和end,表示在每個間隔的起點或是終點發生階躍變化,如果忽略,預設是end。
大前端團隊 > 前端動畫實現 > image2017-11-28 14:30:37.png
橫軸表示時間,縱軸表示動畫完成度(也就是0%~100%)。
第一個圖,steps(1, start)將動畫分為1段,跳躍點為start,也就是說動畫在每個週期的起點發生階躍(即圖中的空心圓 → 實心圓)。由於只有一段,後續就不再發生動畫了。
第二個圖,steps(1, end)同樣是將動畫分為1段,但跳躍點是end,也就是動畫在每個週期的終點發生階躍,也是圖中的空心圓 → 實心圓,但注意時間,是在終點才發生動畫。
第三個圖,steps(3, start)將動畫分為三段,跳躍點為start,動畫在每個週期的起點發生階躍(即圖中的空心圓 → 實心圓)。在這裡,由於動畫的第一次階躍是在第一階段的起點處(0s),所以我們看到的動畫的初始狀態其實已經是 1/3 的狀態,因此我們看到的動畫的過程為 1/3 → 2/3 → 1 。
第四個圖,steps(3, end)也是將動畫分為三段,但跳躍點為end,動畫在每個週期的終點發生階躍(即圖中的空心圓 → 實心圓)。雖然動畫的狀態最終會到達100%,但是動畫已經結束,所以100%的狀態是看不到的,因此我們最終看到的動畫的過程是0 → 1/3 → 2/3。
https://idiotwu.me/study/timi...
steps第一個引數的錯誤的理解:
第一個引數 number 為指定的間隔數,即把動畫分為 n 步階段性展示,估計大多數人理解就是keyframes寫的變化次數
@-webkit-keyframes circle { 0% {background-position-x: 0;} 100%{background-position-x: -400px;} }
@-webkit-keyframes circle { 0% {} 25%{} 50%{} 75%{} 100%{} }
如果有多個幀動畫
@-webkit-keyframes circle { 0% {background-position-x: 0;} 50% {background-position-x: -200px;} 100%{background-position-x: -400px;} }
0-25 之間變化5次,25-50之間 變化5次 ,50-75 之間變化5次,以此類推
應用:
Sprite 精靈動畫 2D遊戲
https://idiotwu.me/css3-runni...
4.3D轉換
父容器:
transform-style(preserve-3d) 建立3D空間
Perspective 景深
Perspective- origin 景深基點
子元素:
Transform 新增函式
rotateX()
rotateY()
rotateZ()
translateZ()
scaleZ()
例項1:3D盒子
http://beiyuu.com/css3-animation
使用例項:
requestAnimationFrame
是什麼
js的一個API
該方法通過在系統準備好繪製動畫幀時呼叫該幀,從而為建立動畫網頁提供了一種更平滑更高效的方法
使用
var handle = setTimeout(renderLoop, PERIOD);
var handle = window.requestAnimationFrame(renderLoop);
window.cancelAnimationFrame(handle);
為什麼出現
css:
- 統一的向下相容策略 IE8, IE9之流
- CSS3動畫不能應用所有屬性 scrollTop值。如果我們希望返回頂部是個平滑滾動效果
- CSS3支援的動畫效果有限 CSS3動畫的貝塞爾曲線是一個標準3次方曲線
緩動(Tween)知識:
Linear:無緩動效果
Quadratic:二次方的緩動(t^2)
Cubic:三次方的緩動(t^3)
Quartic:四次方的緩動(t^4)
Quintic:五次方的緩動(t^5)
Sinusoidal:正弦曲線的緩動(sin(t))
Exponential:指數曲線的緩動(2^t)
Circular:圓形曲線的緩動(sqrt(1-t^2))
Elastic:指數衰減的正弦曲線緩動
超過範圍的三次方緩動((s+1)t^3 – s t^2)
指數衰減的反彈緩動
js:
1.延遲時間固定導致了動畫過度繪製,浪費 CPU 週期以及消耗額外的電能等問題
2.即使看不到網站,特別是當網站使用背景選項卡中的頁面或瀏覽器已最小化時,動畫都會頻繁出現
大前端團隊 > 前端動畫實現 > image2017-11-28 14:31:6.png
相當一部分的瀏覽器的顯示頻率是16.7ms
搞個10ms setTimeout,就會是下面一行的模樣——每第三個圖形都無法繪製
顯示器16.7ms重新整理間隔之前發生了其他繪製請求(setTimeout),導致所有第三幀丟失,繼而導致動畫斷續顯示(堵車的感覺),這就是過度繪製帶來的問題
requestAnimationFrame 與setTimeout相似,都是延遲執行,不過更智慧,跟著瀏覽器的繪製走,如果瀏覽裝置繪製間隔是16.7ms,那我就這個間隔繪製;如果瀏覽裝置繪製間隔是10ms, 我就10ms繪製,瀏覽器(如頁面)每次要重繪,就會通知(requestAnimationFrame)
頁面最小化了,或者被Tab切換當前頁面不可見。頁面不會發生重繪
相容性
Android裝置不支援,其他裝置基本上跟CSS3動畫的支援一模一樣
https://developer.mozilla.org...