@FunC
2017-04-30T16:42:00.000000Z
字数 4802
阅读 3008
前端
优化
(本来想写一篇全面的渲染优化的笔记的,发现学问很深,所以先把这两天学的做个笔记)
要优化渲染,首先要知道页面是怎么呈现的
粗略的可以分为下面几步(括号内为chrome中timeline/performance对应的名字)
1. 构建DOM树(parseHTML)
2. 构建CSSOM树(Recalculate Styles)
3. 将DOM树和CSSOM树组合成Render树
4. 布局(layout):根据Render树计算每个元素的位置、大小等信息,给出矢量数据
5. 绘制(paint):从矢量数据转换成栅格化数据(即填充到像素格子中)+绘制位图(解析图片数据并缩放)
6. 图层复合(composite):在CPU上绘制单独的图层(layer),发送给GPU,让其合成并展示
渲染的流程是分析渲染的关键,请好好观察这个流程:
javascript > style > layout > paint > composite
其中触发部分不一定是js,也可以是css animation/web animation api
值得注意的是,layout>paint>composite的过程
这三步中前面的步骤触发了,后面的一定会触发。但从js开始的整个过程不一定完整走完。下面是一些辨析,可跳过:
- 改变了layout相关属性(如width),则全部触发
- 仅改变了paint相关属性(background-color),不触发layout
- 仅改变图层相关属性,不触发layout和paint(transform,opacity)
注意:三种情况中style每次都会进行计算,因为改变了style(即样式属性发生了变化)
区别:使用flexbox时,resize不用重新计算style,因为style属性没有变化
区别2:如果有resize handler触发style变化,或者触发media query使style变化,都要进行style计算
可到csstriggers查看哪些CSS属性会触发layout、paint和composite
首先,不要浪费时间在微优化上(如使用for循环还是while循环?),因为你不知道js引擎怎么解析你的代码。
JavaScript和渲染主要有关的方面有:
1. 改变样式
2. 制作动画(实质是一系列的改变样式)
为达到60fps的流畅动画、去除掉浏览器的额外工作后,每帧只剩下约10ms可供准备。不要忘了这10ms里还包括style,layout,paint,composite等。(更多信息详见后文app生命周期部分)
16ms每帧,很容易就能联想到定时器:每16ms执行一次动画操作。
然而定时器时间的精度取决于浏览器的内置时钟更新频率,对于IE8及以前是15.6ms,IE9后是4ms。
此外还有异步队列的问题(要等主线程为空时异步队列任务才能入栈)
说在前面:该API从IE10才开始支持
这是浏览器专门用于动画的api,通过该API调用的回调函数会在浏览器对页面画面刷新前(即每一帧前)执行,保证了每一帧前完成样式改变,达到60fps的基本要求。
值得注意的是,回调函数调用时机是页面画面刷新前。只有浏览器自己才知道什么时候刷新,所以某种意义上来说触发时机的不确定的。
如setTimeout、promise和requestAnimationFrame按顺序运行,唯一能确定的是promise在setTimeout之前触发。
另一个例子就是使用 jsbin 时,如果关闭了output,rAF是不会触发的。
前置知识:
1. UI线程(除CSS动画外)与js线程互斥
2. 与样式相关操作一般性能耗费巨大(如触发强制同步layout)
根据1和2,与样式相关操作最好刚好在每一帧之前完成,即应该放在rAF里。在有异步任务时性能提升更明显(如更早发出ajax请求,则更早收到response)
以无限滚屏为例:
如果将大量操作绑定在scroll事件上,将会频发触发回调,造成页面卡顿。
正确做法是scroll只改变标记,通过定时检查标记来判断是否需要执行回调。
var didScroll = false;
$(window).scroll(function() {
didScroll = true;
});
setInterval(function() {
if ( didScroll ) {
didScroll = false;
// Check your page position and then
// Load in more results
}
}, 250)
当然这里的定时器也可用rAF代替
虽然js是单线程,web worker提供了多线程的能力,可将长时间js运算分配给web worker完成。线程之间通过worker.postMessage和监听message事件通讯。
值得注意的是,传递的信息是复制的而不是共享的。如传输对象时,会自动JSON编码/解码
处于线程安全的考虑,web worker只能使用 JavaScript 功能的子集:
Worker 无法使用:
更多内容请阅读Web Workers 的基本信息
以下面的代码为例:
for(var i = 0; i < DOMList.length; i++){
var oldWidth = DOMList[i].offsetWidth;// 第一次读取时不会触发layout,利用的是上一帧layout的信息
DOMList[i].style.width = oldWidth + 10 + 'px';// 改变style后,元素之间的位置信息不明,故第二次读取offset的时候需要触发强制同步layout
}
这段代码的性能非常之差,关键在于offsetWidth的信息是从layout的计算结果中获得的。
当修改了style之后,浏览器就不知道位置信息了,需要在js阶段强制layout来得到offsetWidth的结果,而layout是很耗费性能的。
正确的做法是批量读取完之后再对style进行修改。对于可预测的style变化,不要每次都通过offsetWidth这类api获取,可进行手动计算。
composite是渲染流程的最后一步
javascript > style > layout > paint > composite
如果只触发composite,就能避免触发消耗巨大的layout和paint
想象一个侧边栏组件:如果侧边栏和主页面属于同一个图层的话,每次侧边栏拉入,都会覆盖掉主页面的内容。而折叠侧边栏时,因为不知道侧边栏下面是什么,只能重新layout+paint。
当侧边栏属于独立的图层时,GPU只需要把该图层移动到合适的位置即可,无需layout和paint。
<video>
<iframe>
<canvas>
<webgl>
等元素。其中前2点是最常用的,3d变换兼容性更高。
也许你会说,既然图层这么好,那干脆全部都加上好啦:
*{
will-change:transform;
}
世上没有免费的午餐,创建图层并保存图层信息也会带来资源的消耗。滥用图层将消耗大量的内存资源。(这是空间和时间的取舍)
这个属性使用起来很爽,但使用恰当并不容易。其设计目的是作为最后的优化手段,而不是用来预防性能问题的。所以最佳实践是当元素变化之前和之后通过脚本来切换 will-change 的值。
同时这个属性的原理是提前告诉浏览器可能发生变化的属性,让其提前完成优化工作。所以要预留时间给浏览器优化。当然,当页面主要用途就是动画切换(如相册类),且画面大而复杂的时候,直接在样式设置will-change是合理的。
虽然讨论了很久怎么做到60fps动画,但其实不总需要保持60fps(如更改颜色方案、有滑入/滑出动画时)
R:Response | 100ms
A:Animation | 16ms/帧(实际10~12ms)
I:Idle(闲置)| 50ms
L:Load | 1000ms
当运算时间超过了后方标注的时间,用户将能感知到卡顿
加载(Load)完后,一般处于闲置(Idle)状态,等待用户去互动(Interact),此时是执行要推迟(defer)的任务的绝佳时间,从而达到1s加载完毕的感觉。
通常闲置时间为50ms
以新闻app的文章页面为例(有文本,图片,视频,评论区):
因核心目的为阅读新闻,所以文本必须先加载(用户会花时间阅读文本,留出idle时间)
闲置时间适宜加载的内容:image、videos、comments section等,
不适宜加载的内容:文本和基础且必要的功能
对用户的操作作出反应
1. 简单的:点击按钮
2. 复杂的:会触发动画的(因为要求16ms/帧)
优化方案例子: FLIP(First,Last,Invert,Play)
原理:
1. 一旦浏览器完成了性能消耗高的动画,逆向进行将变得简单。
2. 看起来高耗时的计算位置工作在click到animation begin之间的100ms(response可接受时间)内完成(相比于10ms内完成一帧其实较为宽松)
先计算一开始的位置(First),如可使用getBoundingClientRect()等;计算最后的位置,应用动画(配合opacity,因为性能优秀)
菊花图是否应该在触发视频时发起请求?
答案:否,首先请求时间可能超过16ms。即使不超过16ms,额外的时间也会拖长这一帧使其超过16ms。(这么小的资源完全可以在一开始就加载)
在idle时可加载的内容
答:非首屏内容都可尝试进行加载(包括FLIP的计算)
尽管有一定的时间可以用于计算,但注意时间不是无限的。减少影响的元素的数量有助于优化计算时间
google的性能表现文档
Javascript高性能动画与页面渲染
webkit中会触发layout的api
Web Workers 的基本信息
一篇文章说清浏览器解析和CSS(GPU)动画优化
csstriggers