使用 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 之间转来转去。果断改掉!于是用一个单独的数组来存 x
、y
、d
、v
这些属性。
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 来优化页面性能的更多相关文章
- 前端性能优化之利用 Chrome Dev Tools 进行页面性能分析
背景 我们经常使用 Chrome Dev Tools 来开发调试,但是很少知道怎么利用它来分析页面性能,这篇文章,我将详细说明怎样利用 Chrome Dev Tools 进行页面性能分析及性能报告数据 ...
- 利用图片延迟加载来优化页面性能(jQuery)
图片延迟加载也称懒加载,常用于页面很长,图片很多的页面,以电子商务网站居多,比如大家常上的京东,淘宝,页面以图居多,整个页面少说几百K,多则上兆,如果想一次性加载完成,不仅用户要哭了,服务器也得哭了. ...
- 使用Chrome DevTools的Timeline分析页面性能
随着webpage可以承载的表现形式更加多样化,通过webpage来实现更多交互功能,构建web应用程序已经成为很多产品的首要选择.这种方式拥有非常明显的优势:跨平台.开发便捷.便于部署和维护等等,但 ...
- 页面性能优化和高频dom操作
一.DOM操作影响页面性能的核心问题 通过js操作DOM的代价很高,影响页面性能的主要问题有如下几点: 访问和修改DOM元素 修改DOM元素的样式,导致重绘或重排 通过对DOM元素的事件处理,完成与用 ...
- 高频dom操作和页面性能优化(转载)
作者:gxt19940130 原文:https://feclub.cn/post/content/dom 一.DOM操作影响页面性能的核心问题 通过js操作DOM的代价很高,影响页面性能的主要问题有如 ...
- [WebKit]浏览器的加载与页面性能优化
非常棒.非常系统的一份资料,值得阅读! 原文来自百度泛用户体验. 作者:nwind 本文将探讨浏览器渲染的loading过程,主要有2个目的: 了解浏览器在loading过程中的实现细节,具体都做了什 ...
- 页面性能分析-Chrome Dev Tools
一.分析面板介绍 进行页面性能快速分析的主要是图中圈出来的几个模块功能: Network : 页面中各种资源请求的情况,这里能看到资源的名称.状态.使用的协议(http1/http2/quic...) ...
- web前端页面性能优化
影响用户访问的最大部分是前端的页面.网站的划分一般为二:前端和后台.我们可以理解成后台是用来实现网站的功能的,比如:实现用户注册,用户能够为文章发表评论等等.而前端呢?其实应该是属于功能的表现. 而我 ...
- web前端页面性能优化小结
影响用户访问的最大部分是前端的页面.网站的划分一般为二:前端和后台.我们可以理解成后台是用来实现网站的功能的,比如:实现用户注册,用户能够为文章发表评论等等.而前端呢?其实应该是属于功能的表现. 而我 ...
随机推荐
- 第五篇、css补充二
一.内容概要 1.图标 2.目录规划 3.a标签中的img标签在浏览器中的适应性 4.后台管理系统设置 5.边缘提示框 6.登录页面图标 7.静态对话框 8.加减框 补充知识: line-height ...
- SpringBoot2.0之整合RabbitMQ
案例: Springboot 对RabbitMQ的支持 公共的pom: <project xmlns="http://maven.apache.org/POM/4.0.0" ...
- 算法(Algorithms)第4版 练习 1.3.4
主要思路: 遇到左括号则一直压栈,遇到右括号时则从栈中弹出一个元素. 如果此时栈为空,则返回false. 如果这个元素与右括号不匹配,则返回false. 重复此过程,最后判断栈是否为空,若为空则返回t ...
- jQuery cookie 实现记住用户名和密码功能
jQuery cookie 实现记住用户名和密码功能 HTML代码 <div class="wrap"> <div class="line-top&qu ...
- 英语发音规则---字母组合oo的发音规律
英语发音规则---字母组合oo的发音规律 一.总结 一句话总结:在英语单词中,字母组合oo多数读长音/u:/,少数读短音/ʊ/.另外,还有极少数的特殊情况读/ʌ/, 在英语单词中,字母组合oo多数读长 ...
- Vue2.0 Transition常见用法全解惑
Vue2.0的过渡系统(transition)有了很大的改变,想把1.0的项目迁移到2.0,着实需要费一些功夫,今天我就要把vue2.0的过渡系统的用法搞清楚,因为之前确实踩了不少坑.这里只涉及单元素 ...
- Java_异常_01_org.apache.commons.lang.exception.NestableRuntimeException
异常信息: The type org.apache.commons.lang.exception.NestableRuntimeException cannot be resolved. It is ...
- Canvas drawImage
drawImage() 方法在画布上绘制图像.画布或视频. drawImage() 方法也能够绘制图像的某些部分,以及/或者增加或减少图像的尺寸. 这个方法有三张传参模式,3个参数,5个参数,9个参数 ...
- the referenced script on this behaviour is missing!
1.看看你脚本上挂的某个组件是不是发生了变动,比如被删除了什么的 2.最有可能的是你创建完脚本后,中途改过脚本的名字,致使脚本名字和内部的名字不统一.
- NYOJ-括号配对问题--------待解决,RE
描述 现在,有一行括号序列,请你检查这行括号是否配对. 输入 第一行输入一个数N(0<N<=100),表示有N组测试数据.后面的N行输入多组输入数据,每组输入数据都是一个字符串S(S的 ...