使用 Chrome Timeline 来优化页面性能

有时候,我们就是会不由自主地写出一些低效的代码,严重影响页面运行的效率。或者我们接手的项目中,前人写出来的代码千奇百怪,比如为了一个 Canvas 特效需要同时绘制 600 个三角形,又比如 Coding.net 的任务中心需要同时 watch 上万个变量的变化等等。那么,如果我们遇到了一个比较低效的页面,应该如何去优化它呢?

优化前的准备:知己知彼

在一切开始之前,我们先打开 F12 面板,熟悉一下我们接下来要用到的工具:Timeline:

嗯没错就是它。下面逐一介绍一下吧。区域 1 是一个缩略图,可以看到除了时间轴以外被上下分成了四块,分别代表 FPS、CPU 时间、网络通信时间、堆栈占用;这个缩略图可以横向缩放,白色区域是下面可以看到的时间段(灰色当然是不可见的啦)。区域 2 可以看一些交互事件,例如你滚动了一下页面,那么这里会出现一个 scroll 的线段,线段覆盖的范围就是滚动经过的时间。区域 3 则是具体的事件列表了。

一开始没有记录的时候,所有的区域都是空的。开始统计和结束统计都很简单,左上角那坨黑色的圆圈就是。它右边那个长得像“禁止通行”的按钮是用来清除现有记录的。当有数据的时候,我们把鼠标滚轮向上滚,可以看到区域被放大了:

短短的时间里,浏览器做了这么多事情。对于一般的屏幕,原则上来说一秒要往屏幕上绘制 60 帧,所以理论上讲我们一帧内的计算时间不能超过 16 毫秒,然而浏览器除了执行我们的代码以外,还要干点别的(例如计算 CSS,播放音频……),所以其实我们能用的只有 10~12 毫秒左右。

差不多熟悉操作了,那么就来一下实战吧!假如有一天,你接手了这样一段代码:

<!-- 一段小动画:点击按钮之后会有一个爆炸的粒子效果 -->
<!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 调试工具自带的):

只有 10 FPS……10 FPS……坑爹呢这是!

好吧,打开 Timeline,按下记录按钮,点一下页面中的“点我”,稍微过一会儿停止记录,就会得到一些数据。放大一些,对 jQuery 比较熟悉的同学可以看出来,这些大部分是 jQuery 的函数。我们点一下那个 updateCircle 的区块,然后看下面:

这里告诉我们,这个函数运行了多久、函数代码在哪儿。我们点一下那个链接,于是就跳到了 Source 页:

是不是很震撼,之前这个页面只是用来 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;

效果显著!我们再来看一下精确到行的数据:

优化二:减少不必要的运算

所以最耗时的那句话已经变成了计算 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;

只有加减乘除和开平方运算,每次比原来的时间又少了两毫秒。从流畅的角度来说其实已经可以满帧运行了,然而为什么我还是觉得偶尔会有点卡呢?

优化三:替换 setInterval

既然偶尔会掉帧,那么就看看是怎么掉的呗~原则上来说,在每一次浏览器进行绘制之前,Timeline 里面应该有一个叫 Paint 的事件,就像这样:

看到这些绿色的东西了没?就是它们!看上面的时间轴,虽然代码中 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)';

看起来是不是很爽了?

其实,优化是无止境的,例如我在 init 函数中完全可以不用 jQuery,改用 createDocumentFragment 来拼接元素,这样初始化的时间就可以急剧缩短;调换 updateCircle 中的几个语句的顺序,在 V8 引擎下效率可能会有一定的提升;甚至还可以结合 Profile 面板来分析内存占用,查看浏览器绘图的细节……然而个人感觉并用不到这么极限的优化。对于一个项目来说,如果单纯为了优化而写一些奇怪的代码,是很不合算的。

P.S. 全部的代码在这里,欢迎吐槽:

未优化版 | 优化版

使用 Chrome Timeline 来优化页面性能的更多相关文章

  1. 前端性能优化之利用 Chrome Dev Tools 进行页面性能分析

    背景 我们经常使用 Chrome Dev Tools 来开发调试,但是很少知道怎么利用它来分析页面性能,这篇文章,我将详细说明怎样利用 Chrome Dev Tools 进行页面性能分析及性能报告数据 ...

  2. 利用图片延迟加载来优化页面性能(jQuery)

    图片延迟加载也称懒加载,常用于页面很长,图片很多的页面,以电子商务网站居多,比如大家常上的京东,淘宝,页面以图居多,整个页面少说几百K,多则上兆,如果想一次性加载完成,不仅用户要哭了,服务器也得哭了. ...

  3. 使用Chrome DevTools的Timeline分析页面性能

    随着webpage可以承载的表现形式更加多样化,通过webpage来实现更多交互功能,构建web应用程序已经成为很多产品的首要选择.这种方式拥有非常明显的优势:跨平台.开发便捷.便于部署和维护等等,但 ...

  4. 页面性能优化和高频dom操作

    一.DOM操作影响页面性能的核心问题 通过js操作DOM的代价很高,影响页面性能的主要问题有如下几点: 访问和修改DOM元素 修改DOM元素的样式,导致重绘或重排 通过对DOM元素的事件处理,完成与用 ...

  5. 高频dom操作和页面性能优化(转载)

    作者:gxt19940130 原文:https://feclub.cn/post/content/dom 一.DOM操作影响页面性能的核心问题 通过js操作DOM的代价很高,影响页面性能的主要问题有如 ...

  6. [WebKit]浏览器的加载与页面性能优化

    非常棒.非常系统的一份资料,值得阅读! 原文来自百度泛用户体验. 作者:nwind 本文将探讨浏览器渲染的loading过程,主要有2个目的: 了解浏览器在loading过程中的实现细节,具体都做了什 ...

  7. 页面性能分析-Chrome Dev Tools

    一.分析面板介绍 进行页面性能快速分析的主要是图中圈出来的几个模块功能: Network : 页面中各种资源请求的情况,这里能看到资源的名称.状态.使用的协议(http1/http2/quic...) ...

  8. web前端页面性能优化

    影响用户访问的最大部分是前端的页面.网站的划分一般为二:前端和后台.我们可以理解成后台是用来实现网站的功能的,比如:实现用户注册,用户能够为文章发表评论等等.而前端呢?其实应该是属于功能的表现. 而我 ...

  9. web前端页面性能优化小结

    影响用户访问的最大部分是前端的页面.网站的划分一般为二:前端和后台.我们可以理解成后台是用来实现网站的功能的,比如:实现用户注册,用户能够为文章发表评论等等.而前端呢?其实应该是属于功能的表现. 而我 ...

随机推荐

  1. 《thinking in java》 接口与内部类

    书本上的例子 改编后的. package test2; class A{ interface B{void f();} public class BImp implements B{public vo ...

  2. Java -- 封装访问控制级别,包, instanceof 运算符, 初始化块

    1. 可以用 package name1.name2; 显式的定义包名, *.class文件位置应该对应包 name1 name2 的目录下. 2. instanceof 运算符 Object obj ...

  3. 封装 oschina.net 表情选择

    1. [代码]jquery.facial.js //从OSCHINA.NET 提取出来的 表情选择 插件 by zhouxiang //如果有不满足的地方 可以自己改改 没事随便写写的 style 和 ...

  4. 更新github上代码

    前面一篇已经实现首次上传代码到github了,本篇继续讲如何把本地更新的代码同步更新到github上 一.clone代码 1.把大神的代码clone到本地,或者clone自己github上的代码,使用 ...

  5. Java之 将程序打包成jar包

    准备材料: 1.java文件: Helloworld.java package com.ray; public class HelloWorld{ public static void main(St ...

  6. BEC listen and translation exercise 37

    You're supposed to do that before 10.30 in the morning, but obviously, if it's an emergency, you can ...

  7. web前端绘制0.5像素的几种方法

    最近完成了公司安排的移动web触屏开发,期间涉及到在移动设备上显示线条,最开始采用PC常用的css board属性来显示1个像素的线条,但是发现在移动设备上并不美观,参考淘宝.京东的触屏发现它们均是采 ...

  8. MarkDown不支持图片放缩。。

    ![](http://images2015.cnblogs.com/blog/573995/201604/573995-20160430162211050-1357272526.jpg =100x20 ...

  9. LeetCode Majority Element I

    原题链接在这里:https://leetcode.com/problems/majority-element/ 题目: Given an array of size n, find the major ...

  10. 【学习笔记-中国剩余定理】POJ1006 Biorhythms

    Biorhythms Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 139500   Accepted: 44772 Des ...