最近在做一個場景動畫,有一個歡迎界面和一個主動畫界面,兩個界面之間的連接通過一個進度條來完成,當進度條完成,提供通往主動畫的按鈕。
畫面會從一個個的場景移動過去,用戶可通過點擊抽獎、查看氣泡商鋪等進行交互,同時可拖動畫面,前移或后退。該項目中,出了主動畫,還有人物場景對話的動畫等,性能的優化、用戶的體驗變得尤為重要,這里總結一下在開發過程中使用的一些性能、體驗優化方法。
1、動畫
a、優先采用requestanimationframe,實現動畫幀的並發渲染;
b、做減法:兼容低版本瀏覽器( a中的元素不生效,通過setTimeout實現動畫),保留主動畫性能,去除重要性不大的動畫(跑馬燈、過程小動畫等);
c、大圖動畫性能消耗非常大,使用translate3d實現GPU加速,動畫結束、暫停時,切換回2d,取消加速;
d、按需加載/卸載動畫;
a、優先采用requestanimationframe,實現動畫幀的並發渲染;
b、做減法:兼容低版本瀏覽器( a中的元素不生效,通過setTimeout實現動畫),保留主動畫性能,去除重要性不大的動畫(跑馬燈、過程小動畫等);
c、大圖動畫性能消耗非常大,使用translate3d實現GPU加速,動畫結束、暫停時,切換回2d,取消加速;
d、按需加載/卸載動畫;
e、每個動畫幀處理函數簡化,盡量減少或者去除回流、重繪。
2、加載、用戶體驗優化
a、首屏優先加載,保證用戶體驗的流暢性:優先歡迎界面(即首屏)圖片資源的加載,所有圖片loaded以后,再啟動主動畫資源的加載,與進度條動畫;
b、資源的預加載:在進入主動畫之前,進行主動畫各資源的加載,當完成加載時,再promise結束進度條動畫;
a、首屏優先加載,保證用戶體驗的流暢性:優先歡迎界面(即首屏)圖片資源的加載,所有圖片loaded以后,再啟動主動畫資源的加載,與進度條動畫;
b、資源的預加載:在進入主動畫之前,進行主動畫各資源的加載,當完成加載時,再promise結束進度條動畫;
c、
常規優化:雪碧圖、壓縮、base64等;
d、存儲dom變量,減少dom tree的查找等;
e、限頻。
3、說明
3.1、requestAnimationFrame
它是一種高級的方法,存在兼容性問題。主要運作方式是瀏覽器要進行繪制的時候(一般16.7ms一次繪制),會通知requestAnimationFrame們,requestAnimationFrame們就跟它一起繪制。這里有幾個好處,多個requestAnimationFrame可以同時進行,而setTimeout需要獨立繪制;頁面切換等情況,瀏覽器不再繪制該頁面,requestAnimationFrame也停止了繪制,與瀏覽器同步,資源很省。相對setTimeout,是一個js的執行棧,只能串行執行,並且會影響其他js的處理。所以,使用前者,性能更佳,更流暢,交互體驗更佳。特別是多個動畫同時進行時,前者毫無壓力,后者表示卡頓厲害。兼容代碼如下,引自張鑫旭的log,感興趣的同學可以仔細讀一下:鏈接
1 /* requestAnimationFrame.js 2 * by zhangxinxu 2013-09-30 3 */ 4 (function() { 5 var lastTime = 0; 6 var vendors = ['webkit', 'moz']; 7 for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 8 window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame']; 9 window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || // name has changed in Webkit 10 window[vendors[x] + 'CancelRequestAnimationFrame']; 11 } 12 13 if (!window.requestAnimationFrame) { 14 window.requestAnimationFrame = function(callback) { 15 var currTime = new Date().getTime(); 16 var timeToCall = Math.max(0, 16.7 - (currTime - lastTime)); 17 var id = window.setTimeout(function() { 18 callback(currTime + timeToCall); 19 }, timeToCall); 20 lastTime = currTime + timeToCall; 21 return id; 22 }; 23 } 24 if (!window.cancelAnimationFrame) { 25 window.cancelAnimationFrame = function(id) { 26 clearTimeout(id); 27 }; 28 } 29 }());
3.2、項目中的減法
這種要做減法的情況下,最佳的體驗是自己把動畫都完成好,直接演示給產品、設計童鞋看,他們就能從中取舍了,沒有任何的扯皮、分歧。tips:當然動畫不需要跟實際用的一致,幾個特性差不多就好,比如主動畫背景圖非常大,多個動畫並行,且持續時間比較長。
3.3、關於大圖動畫
這種動畫渲染的性能消耗往往非常大,考慮開啟GPU來加速圖層的渲染,但是由於GPU的渲染又是非常耗費內存、電量的,所以在不需要移動的情況下,需要關閉GPU,防止瀏覽器的崩潰。在該項目中,當用戶進行游戲交互等,觸發主動畫暫停時,將通過設置translate2d,取消加速;啟動時,再開啟。
3.4、按需加載/卸載動畫
對於場景動畫來說,有些動效,只有出現在視口時,才有播放的必要性;離開視口,就可以關閉。通過判斷動畫當前的位置,可以實現動畫的按需加載。
3.5、重繪、回流
動畫幀內減少處理,即避免長時間的js執行;減少回流、重繪在哪里都適用,可以用transform等操作,來替代position;left等等的操作。當需要display:none的情況下(回流),使用opacity:0(重繪);或者visibility:hidden(重繪),將更優。回流的性能消耗要遠大於重繪。
3.6、首屏優先加載
在網速很可觀的情況下,完全可以同時加載整站的資源,僅將首屏的資源前置即可。
但在網絡狀況很抓狂的情況下(如3g下),這種大量圖片、音頻的頁面,就需要考慮資源分批啟動加載的必要性了。在網速欠佳,但是服務器允許多並發的情況下,同時可以請求多個資源,此時帶寬及其有限的,雖然整體的加載時間沒變化,但是首屏的加載時間卻延長了。如當前帶寬時750kb/s,10個資源一起請求,則每個分到75kb/s,150kb的圖片,需要加載2s;如果只有3個資源一起請求,分到250kb/s,需加載0.6s。因此,為了體驗更佳,首屏的資源加載完畢,再開啟主動畫資源加載。
3.7、資源的預加載
因為主動畫中的交互、資源較多,需要資源穩定以后才能有更好的用戶體驗,所以這里提供了資源的預加載。
圖片預加載:
newImgObjs[i] = new Image(); newImgObjs[i].src = animationImgs[i]; newImgObjs[i].onload = function() { loadeds++; if (loadeds == newImgObjs.length) { self.barObj.completeWelcomePromise.resolve(); console.log('圖片資源已經加載完成'); } }; newImgObjs[i].onerror = function() { loadeds++; if (loadeds == newImgObjs.length) { self.barObj.completeWelcomePromise.resolve(); console.log('圖片資源已經加載完成'); } };
$audio[0].src = imgPath; $audio.on('canplaythrough', function() { loadeds++; if (loadeds == (newImgObjs.length + 1)) { self.barObj.completePromise.resolve(); console.log('音頻資源已經加載完成'); } }); $audio[0].onerror = function(e) { loadeds++; if (loadeds == (newImgObjs.length + 1)) { self.barObj.completePromise.resolve(); console.log('音頻資源已經加載完成'); } };
3.8、其他幾種
限頻、dom操作、雪碧圖等不再多說。關於響應式下的雪碧圖處理,
上一篇博客有提供系統的解決方案。
4、promise的使用
在該項目中,promise的使用較為頻繁,包括ajax請求、圖片的預加載、進度條的處理等。
// 資源處理:預加載、監控加載完成、渲染 $.when(self.cmsInfoPromise, self.goodsInfoPromise, self.barObj.completeWelcomePromise) .done(function() { // 進度條動效 self.barAnimation(); self.handleResource(); self.renderStores(); });