使用 Chrome Timeline 來優化頁面性能

使用 Chrome Timeline 來優化頁面性能

有時候,咱們就是會不禁自主地寫出一些低效的代碼,嚴重影響頁面運行的效率。或者咱們接手的項目中,前人寫出來的代碼千奇百怪,好比爲了一個 Canvas 特效須要同時繪製 600 個三角形,又好比 Coding.net 的任務中心須要同時 watch 上萬個變量的變化等等。那麼,若是咱們遇到了一個比較低效的頁面,應該如何去優化它呢?css

優化前的準備:知己知彼

在一切開始以前,咱們先打開 F12 面板,熟悉一下咱們接下來要用到的工具:Timeline:html

2

嗯沒錯就是它。下面逐一介紹一下吧。區域 1 是一個縮略圖,能夠看到除了時間軸之外被上下分紅了四塊,分別表明 FPS、CPU 時間、網絡通訊時間、堆棧佔用;這個縮略圖能夠橫向縮放,白色區域是下面能夠看到的時間段(灰色固然是不可見的啦)。區域 2 能夠看一些交互事件,例如你滾動了一下頁面,那麼這裏會出現一個 scroll 的線段,線段覆蓋的範圍就是滾動通過的時間。區域 3 則是具體的事件列表了。react

一開始沒有記錄的時候,全部的區域都是空的。開始統計和結束統計都很簡單,左上角那坨黑色的圓圈就是。它右邊那個長得像「禁止通行」的按鈕是用來清除現有記錄的。當有數據的時候,咱們把鼠標滾輪向上滾,能夠看到區域被放大了:jquery

3

短短的時間裏,瀏覽器作了這麼多事情。對於通常的屏幕,原則上來講一秒要往屏幕上繪製 60 幀,因此理論上講咱們一幀內的計算時間不能超過 16 毫秒,然而瀏覽器除了執行咱們的代碼之外,還要乾點別的(例如計算 CSS,播放音頻……),因此其實咱們能用的只有 10~12 毫秒左右。算法

差很少熟悉操做了,那麼就來一下實戰吧!假若有一天,你接手了這樣一段代碼:swift

<!-- 一段小動畫:點擊按鈕以後會有一個爆炸的粒子效果 --> <!DOCTYPE html> <html> <head>  <meta charset="utf-8">  <title>Test</title>  <style>  .main {  position: relative;  width: 500px;  height: 500px;  background: #000;  overflow: hidden;  }  .circle {  position: absolute;  border-radius: 50%;  border: 1px solid #FFF;  width: 8px;  height: 8px;  }  </style> </head> <body>  <div class="main"></div>  <hr>  <button onclick="showAnimation()">點我</button>  <script src="jquery.min.js"></script>  <script src="animation.js"></script> </body> </html> 
// animation.js

// 粒子總數
var COUNT = 500;
// 重力
var G = -0.1;
// 摩擦力
var F = -0.04;

function init() {
 for (var i = 0; i < COUNT; i++) {  var d = Math.random() * 2 * Math.PI;  var v = Math.random() * 5;  var circle = $('<div id="circle-' + i + '" class="circle" data-x="250" data-y="250" data-d="' + d + '" data-v="' + v + '"></div>');  circle.appendTo($('.main'));  } } function updateCircle() {  for (var i = 0; i < COUNT; i++) {  var x = parseFloat($('#circle-' + i).attr('data-x'));  var y = parseFloat($('#circle-' + i).attr('data-y'));  var d = parseFloat($('#circle-' + i).attr('data-d'));  var v = parseFloat($('#circle-' + i).attr('data-v'));  var vx = v * Math.cos(d);  var vy = v * Math.sin(d);  if (Math.abs(vx) < 1e-9) vx = 0;  // 速度份量改變  vx += F * Math.cos(d);  vy += F * Math.sin(d) + G;  // 計算新速度  v = Math.sqrt(vx * vx + vy * vy);  if (vy > 0) d = Math.acos(vx / v);  else d = -Math.acos(vx / v);  // 位移份量改變  x += vx;  y += vy;  $('#circle-' + i).attr('data-x', x);  $('#circle-' + i).attr('data-y', y);  $('#circle-' + i).attr('data-d', d);  $('#circle-' + i).attr('data-v', v);  $('#circle-' + i).css({'top': 400 - y, 'left': x});  } } var interval = null; function showAnimation() {  if (interval) clearInterval(interval);  $('.main').html('');  init();  interval = setInterval(updateCircle, 1000 / 60); } 

效果以下(右上角的 FPS 計數器是 Chrome 調試工具自帶的):數組

1

只有 10 FPS……10 FPS……坑爹呢這是!瀏覽器

4

好吧,打開 Timeline,按下記錄按鈕,點一下頁面中的「點我」,稍微過一下子中止記錄,就會獲得一些數據。放大一些,對 jQuery 比較熟悉的同窗能夠看出來,這些大部分是 jQuery 的函數。咱們點一下那個 updateCircle 的區塊,而後看下面:緩存

5

這裏告訴咱們,這個函數運行了多久、函數代碼在哪兒。咱們點一下那個連接,因而就跳到了 Source 頁:markdown

6

是否是很震撼,以前這個頁面只是用來 Debug 的,沒想到如今竟然帶了精確到行的運行時間統計。固然,這個時間是當前這一行在「剛纔咱們點擊的區塊對應的執行時間段」中運行的時間。因此咱們就拿最慢的幾句話來下手吧!

優化一:減小 DOM 操做

看到這幾行代碼,第一反應是:mdzz。原本 DOM 操做就慢,還要在字符串和 float 之間轉來轉去。果斷改掉!因而用一個單獨的數組來存 xydv 這些屬性。

var objects = [];
// 在 init 函數中
objects.push({
 x: 250,  y: 250,  d: d,  v: v }); // 在 updateCircle 函數中 var x = objects[i].x; var y = objects[i].y; var d = objects[i].d; var v = objects[i].v; // …. objects[i].x = x; objects[i].y = y; objects[i].d = d; objects[i].v = v; 

7

效果顯著!咱們再來看一下精確到行的數據:

8

優化二:減小沒必要要的運算

因此最耗時的那句話已經變成了計算 vx 和 vy,畢竟三角函數算法比較複雜嘛,能夠理解。至於後面的三角函數爲何那麼快,我猜多是 Chrome 的 V8 引擎將其緩存了(這句話不保證正確性)。然而不知道你們有沒有發現,其實計算 d 徹底不必!咱們只須要存 vx 和 vy 便可,不須要存 v 和 d

// init
var vx = v * Math.cos(d);
var vy = v * Math.sin(d);
objects.push({
 x: 250,  y: 250,  vx: vx,  vy: vy }); // updateCircle var vx = objects[i].vx; var vy = objects[i].vy; // 計算新速度 var v = Math.sqrt(vx * vx + vy * vy); if (Math.abs(vx) < 1e-9) vx = 0; // 速度份量改變 vx += F * vx / v; vy += F * vy / v + G; // …. objects[i].vx = vx; objects[i].vy = vy; 

9

只有加減乘除和開平方運算,每次比原來的時間又少了兩毫秒。從流暢的角度來講其實已經能夠滿幀運行了,然而爲何我仍是以爲偶爾會有點卡呢?

優化三:替換 setInterval

既然偶爾會掉幀,那麼就看看是怎麼掉的唄~原則上來講,在每一次瀏覽器進行繪製以前,Timeline 裏面應該有一個叫 Paint 的事件,就像這樣:

10

看到這些綠色的東西了沒?就是它們!看上面的時間軸,雖然代碼中 setInterval 的長度是 1000/16 毫秒,可是其實根本不能保證!因此咱們須要使用 requestAnimationFrame 來代替它。這是瀏覽器自帶的專門爲動畫服務的函數,瀏覽器會自動優化這個函數的調用時機。而且若是頁面被隱藏,瀏覽器還會自動暫停調用,有效地減小了 CPU 的開銷。

// 在 updateCircle 最後加一句
requestAnimationFrame(updateCircle);
// 去掉所有跟 setInterval 有關的句子,把 showAnimation 最後一句直接改爲這個
updateCircle();

咱們至少能夠保證,咱們每算一次,屏幕上就會顯示一次,所以不會掉幀(前提是每計算一次的時間小於 12ms)。可是雖然計算時間少了,瀏覽器重計算樣式、繪製圖像的時間但是一點都沒變。能不能再作優化呢?

優化四:使用硬件加速、避免反覆查找元素

若是咱們用 transform 來代替 left 和 top 來對元素進行定位,那麼瀏覽器會爲這個元素單首創立一個合成層,專門使用 GPU 進行渲染,這樣能夠把重計算的代價降到最低。有興趣的同窗能夠研究一下「CSS 硬件加速」的機制。同時,咱們能夠緩存一下 jQuery 的元素(或者 DOM 元素),這樣不用每次都從新查找,也能稍微提升一點效率。若是把元素緩存在 objects 數組中,那麼連 id 都不用寫了!

// init
var circle = $('<div class="circle"></div>'); objects.push({  x: 250,  y: 250,  vx: vx,  vy: vy,  // 其實能夠只存 DOM,不存 jQuery 對象  circle: circle[0] }); // updateCircle 裏面 for 循環的最後一句話替換掉 objects[i].circle.style.transform = 'translate(' + x + 'px, ' + (400 - y) + 'px)'; 

11

看起來是否是很爽了?

其實,優化是無止境的,例如我在 init 函數中徹底能夠不用 jQuery,改用 createDocumentFragment 來拼接元素,這樣初始化的時間就能夠急劇縮短;調換 updateCircle 中的幾個語句的順序,在 V8 引擎下效率可能會有必定的提高;甚至還能夠結合 Profile 面板來分析內存佔用,查看瀏覽器繪圖的細節……然而我的感受並用不到這麼極限的優化。對於一個項目來講,若是單純爲了優化而寫一些奇怪的代碼,是很不合算的。

P.S. 所有的代碼在這裏,歡迎吐槽:

未優化版 | 優化版

相關文章
相關標籤/搜索