浏览器渲染优化与Chrome开发者工具
为何我们力求达到60帧/秒的目标,创建帧涉及哪些内容,更改属性会以不同的方式影响到性能。本文主要讲制作帧需要哪些内容,不同的样式如何对管道产生影响,知道如何根据RAIL来安排性能任务的优先级,并且知道应用的生命周期以及如何使用Chrome时间线工具,导致出现卡顿的原因。
关键渲染路径
单个帧渲染流程
如今多数设备刷新屏幕的频率都是60帧/秒,如果浏览器花费太长时间才能显示一帧,就会丢失一帧,帧速度将会下降,用户就会看到卡顿现象。如果情况很糟糕的话,整个屏幕就会卡住,这就是最坏情况了。
如果你不知道浏览器是如何渲染帧的话,则无法优化应用的帧率,所以你需要了解当网页第一次被加载时是如何形成的,现在我将简单介绍下这个流程。如何你想深入了解网页是如何构建的。
如何构成帧
首先浏览器向服务器发出获取请求,服务器做出响应并发送一些html,此时,浏览器采取非常智能的措施并提前解析,我们关系的是,他会解析文档并向我们提供这些节点。
除了dom还有css,dom结合css,获得新的树,叫做渲染树。
渲染树看起来和dom非常相似
如何css设置display:none,那么将会从渲染树里移除。
浏览器知道哪些规则适用于相关元素后,就开始布局。也就是计算元素会占用多少空间。位于屏幕的什么位置。layout
网络布局模型意味着某个元素可以影响到其他元素。
该流程的下一步是矢量到光栅,光栅器需要执行绘制调用以便填充像素。
当完成后,效果是这样的
布局和绘制
对于开发者来说,通常帧是这样的
不同的样式影响到了管道的不同部分,进而影响到应用的性能。
App的生命周期
RAIL
我们称网络应用的生命周期四大领域称为:RAIL
初始加载操作应该在1秒钟内完成,当加载完成,通常处于闲置状态,等待用户采取操作,这时候我们就抓住机会处理那些,为了满足1秒钟加载目标而推迟的工作。通常这些闲置时间段时长约为50ms,虽然可能一次出现多个闲置时间段,这些闲置时间段是完成繁杂任务的极佳机会。以便用户做出互动时一切都顺畅快速。那么响应程度需要达到什么级别呢?研究表明存在100ms上限,在用户按下屏幕上的某项内容后
更具有挑战性的是此时用户需要实现动画效果,最具有挑战性的问题通常都是需要达到60fps,1000ms/60=16ms。实际上,因为浏览器也需要处理时间,所以只有10-12毫秒的时间。
开发者工具
演示页面:
https://www.html5rocks.com/static/demos/parallax/demo-1a/demo.html
顶部的帧柱表示每秒的帧数,一条线表示60fps,另一条表示30fps。如果我们要到达60fps,所以帧柱都应该在这条线下方。
下方有大量的信息,说明了每帧里,我们是如何花费时间的。
在详情部分可以看到我们之前讨论的管道部分,包括JavaScript、样式计算、布局、布局管理、绘制和合成。
timeline
两种视图,信息是一样的,只是显示方式不同而已。
flame chart(火焰图)
图标从上往下延伸,如果管道的某个部分触发了其他内容,则在父记录的下方显示子记录。
parallax.js中的这个函数调用时滚动事件的子项
蓝色:html解析
紫色:Recalculate Style、layout
绿色:paint、composite
瀑布视图
柱帧越长表示任务耗时越长。
放大这部分后,你会看到这里触发了动画帧,调用了其他javascript,导致了calc style、导致了layout。你可以明白哪些事件导致了哪些事件。
这里发现JavaScript占用了很多事件,放大后,你还可以详细了解这些帧里发生的情况。
可以看到是什么操作,时长多少、开始时间,然后再稍微细分了下、时间本身和任何子记录,最后看到任务是在代码的何处触发。
recalculate style会显示受到影响的元素数量
layout会显示树的大小,范围,开始位置、代码何处触发了布局。
例题分析
找出有问题的代码
案例一:http://output.jsbin.com/saxalu/2/quiet#
该网站的记录看起来非常接近60帧/秒。但并非完全达到了,所有紫色柱表明可能出现了太多的布局事件。放大后很明显能看出这些布局存在问题。
warning:Force synchronous layout is a possible persormance bottleneck
来自脚本quiet的第172行,罪魁祸首就是它。点击查看导致强制布局的函数。
打开看到了,每帧中由requestAnimationFrame调用的函数导致的。
案例2:
https://d17h27t6h515a5.cloudfront.net/topher/2017/October/59defb6e_index/index.html
我们在时间线里看到了很多绿色柱状,明显存在绘制问题,放大其中一帧看看
看来每帧以脚本开始,有个Animation Frame Fired事件,紧着着是样式计算和绘制事件,似乎是JavaScript问题,因为如果问题来自css,那么久不会看到Animation Frame Fired事件
卡顿原因及如何解决
针对动画的优化JS
动画的时间上限16ms,实际上只有约10ms的时间来执行所有操作并准备好帧包括运行布局、合成和绘制。
尽早执行JavaScript,在每帧的开始最适合运行JavaScript,因为它会导致样式计算、布局和绘制操作。
RequestAnimationFrame
这个API可以让你的JavaScript在每帧的合适时间运行。RequestAnimationFrame应该是你创建动画时的必备工具。要达到60fps浏览器根本没有渲染时间,在渲染每一帧的同时,浏览器还有一些额外的工作要做,因此我们应该控制在10ms之内完成渲染工作。
一帧的JavaScript部分通常应该最长保持在3到4秒,因为之后还有其他工作,比如样式计算,图层管理和渲染层合并。
假设有关样式的工作,然后出现需要处理的JavaScript,在继续处理其他任务之前,浏览器需要处理插进来的JavaScript,新来的JavaScript可能导致该帧的工作重新反工。可能导致丢失了这一帧。
所以尽早在每一帧的开始执行。这样尽量给浏览器留出足够的时间来运行代码。
网络上很多用来实现动画的旧版代码使用setTimeout。
这两个函数存在的问题是JavaScript引擎,安排这两个函数根本不会关注渲染管道,如果你想等待一段时间或经常重复执行某些任务,这两个函数派上用场,但是它们不适合动画。
RequestAnimationFrame用法:
调用它,并告诉它要调用哪个函数,在RequestAnimationFrame结束时再安排下个动画。浏览器会知道何时运行,如何运行。IE9不支持
要达到60帧每秒,你需要让所有工作都在16ms内完成,不仅仅是JavaScript而是一帧的所有工作。
例题:找出运行时间长的js
http://output.jsbin.com/feloni/3/quiet
很明显看到分别点击两个事件,并且第一个运行时间更长
第一次点击后出现重新计算样式事件,然后是布局事件
点击第二个事件,又是一个重新计算样式和布局事件,两组重新计算样式和布局事件基本上时长一样。明显表明在此实例中,无论你怎么排序数据,依然都需要写出整个结果表格。这就需要重新计算样式,重新布局和重新绘制所以内容。
web workers
web worker可以在单独的线程上处理,而主线程只负责提供60帧/秒的
JS内存管理
JavaScript会收集垃圾,对于我们开发者来说,我们不用担心指针,删除对象。JavaScript引擎需要自己来处理这些垃圾。当它运行垃圾收集器时,就不会运行别的了,这样会在渲染页面时出现可见的暂停现象。
可以在这里看到这个锯齿状图案,出现这个下降趋势就表明垃圾收集器正在运行,这里我们要看两项内容,首先,有很多陡坡,这表明我们非常快速地分配了大量的内存,其次,当垃圾收集器运行时,它会归零吗,如果没有,可能出现了内存泄漏情况
如果你发现因为垃圾收集(GC)而丢失了帧,可以参阅下面资源:
https://www.smashingmagazine.com/2012/11/writing-fast-memory-efficient-javascript/
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Memory_Management
http://buildnewgames.com/garbage-collector-friendly-code/
样式计算、布局和绘制
你已经学会了查找和解决问题,JavaScript可以正常运行了,但只是制作帧的一小部分。
下面介绍样式计算过程中,找到性能问题并学会解决这些问题。
选择器匹配
一种非常的方法是bem即块元素修饰符。它会对样式元素使用单个类的名称。并且对性能更有优势。因为对现代浏览器来说,通常类匹配是最快选择器。
Block
Element
Modifier
例如:.box--three
复杂的css选择器会给浏览器增加工作量。选择器越复杂,浏览器就更需要多次在DOM树上上下下移动,移动次数越多,找到正确元素花费的时间就越长
例题:
https://s3.cn-north-1.amazonaws.com.cn/static-documents/nd001/Box+Style+Change.htm
发现self time时间太长,根本达不到60帧/秒
解决办法:
- 减少受影响的元素
- 降低选择器复杂性
更多有关区块元素修饰符的信息
https://en.bem.info/methodology/key-concepts/
https://www.sitepoint.com/bem-smacss-advice-from-developers/
强制同步布局FSL
force syncronouce layout
http://output.jsbin.com/aqavin/2/quiet
问题代码:
浏览器必须计算offsetWidth,这就需要布局。每次更改样式,刚刚执行的布局流程都会变得无效,因为你更改了样式,现在浏览器就要重新完成一遍,这一错误的代码很高。如果你触发了强制同步布局,开发者工具会在这里用感叹号标出来。
在帧视图中,你会在布局记录的右上角看到红色的三角形。
使布局跑到样式计算的前方
下面查看哪些css会触发布局。
https://csstriggers.com/
停止FSL的策略
在JavaScript阶段先读取布局属性,意味着你将使用上一帧的布局,然后进行所有样式更改。
如何不在webKit中触发布局:
http://gent.ilcore.com/2011/03/how-not-to-trigger-layout-in-webkit.html