Web网页应该尽量避免不稳定性。
多数设备刷新屏幕的频率为每秒60帧,即60fps
渲染流程
注意,复合层上方的元素也会变为单独的图层(隐式复合)
对于样式改变,导致的渲染流程:
对于JavaScript部分,指的是能够引起外观的变化,不仅可以通过JavaScript来改变页面外观,也可以使用CSS动画或者Web Animation API来实现。
改变外观后可能会需要重新计算Style属性(比如媒体查询等),有可能不需要。
改变的CSS属性不同,后续触发流程也不同。
transform
、opacity
等会生成单独的图层的属性,不会再触发Reflow和Repaint,只需触发Composite步骤CSS属性的改变应该尽可能触发少量的工作,避免Reflow和Repaint,提高性能。
(1)第一题
渲染树中只包含了最终显示在屏幕上的元素内容,它与DOM树并不完全相同,比如它不会包含display:none
的元素,不会包含标签中的元素。
所以这道题目的答案是A,而其他的属性,虽然不会占据屏幕空间,但是还是属于页面的一部分,仍然会包含在渲染树中。
(2)第二题
无论是改变body
还是改变其中内部的div
的宽度,浏览器都会做出最坏的打算,就是需要对全部DOM元素的样式进行计算,对整个文档进行Reflow和Repaint。所以答案是C
(3)第三题
题目中是一个弹性部局的容器(flex),但改变容器的大小,内部元素的尺寸会随机发生变化。当新的页面渲染时,浏览器会经历那些步骤?
弹性容器的尺寸发生变化,内部元素的尺寸会发生变化,但是它们的Style并没有发生变化(因为宽度都是弹性确定的,而非固定值,并且不涉及媒体查询短点改变样式的条件),所以不会重新计算Style,但是由于宽度发生了变化,所以后续的Layout、Paint、Composite都会执行。
(4)第四题
会触发Layout+Paint+Composite的属性很多,比如margin
、width
等
触发Paint+Composite的属性有color
、background
等
触发Composite的属性变transform
、opacity
等
可以通过CSS Triggers网站来查找CSS属性分别会触发哪些过程。
网络应用的生命周期包含了四个主要阶段:RAIL
按照时间先后顺序:
学习使用Chrome的Devtools中的性能分析面板Performance(以前的Timeline)
现在的面板和课程讲解中已经发生了很大声变化,但是大体思路是相同的,录制之后再中间的Main
的下拉部分去分析什么导致的卡顿
它是倒火焰图,上方的行为调用了下方的命令,不同的行为有不同的颜色表示,黄色是JavaScript脚本,紫色是重新计算样式,绿色是绘制
可以找到哪个行为事件过长(大于16ms)导致卡顿,然后进行分析、优化
JavaScript运行时并不是我们编写的样子,而是通过JavaScript解释器提供的即时编译器,编译为更有效率的代码来执行的,所以编写代码时没有必要去做一些while
和for
谁更快之类的微优化。
JavaScript是渲染管道的开始,后续有重新计算样式、重排、重绘、复合的过程。要想不卡顿需要达到60fps,所以每一帧只有16ms的时间,分配给JavaScript部分的也就只有10-12ms。所以在每一帧渲染开始时,应当尽早执行JavaScript函数,这样才能够有足够的时间完成后续的过程。
在执行JavaScript动画时,应当使用requestAnimationFrame
来代替setTimeout
/setInterval
,因为后者不会关注渲染管道的流程,而前者会自动的在每一帧开始时运行JavaScript函数。
function animate() {
// do something here
requestAnimationFrame(anmiate)
}
requestAnimationFrame(anmiate)
可以使用上节课提到的Chrome的performance
面板分析JavaScript的运行时间和内存占用情况。JavaScript会自动进行内存回收,所以我们不必担心指针、删除对象、局部变量的内存占用问题。可以通过performance
分析是否存在内存泄漏等情况。
当有大量耗时的计算任务时,可以考虑使用Web Worker,将耗时的任务移动到Web Worker线程进行计算,而主线程(Main Thread)负责渲染流畅的UI。
Web Worker可以在不同于主窗口的线程下,完全独立的操作系统线程下手运行JavaScript。主线程与Web Worker线程之间通过postMessage
和onmessage
事件进行通信,各个Worker之间不能通信。
主线程:
const myWorker = new Worker('./scripts/worker.js');
myWorker.postMessage(data)
myWoker.onmessage = function(e) {
console.log(e.data)
}
Worker线程 worker.js
// 通过importScipts导入其他脚本
importScipts('other.js')
this.onmessage = function(e) {
console.log(e.data)
this.postMessage(data)
}
重新计算样式(Recalculate Style)造成的性能代价与元素数量基本上是线性增长的关系。
可以采用BEM规则来为CSS的类命名,更加模块化、可复用、可读性好,且新跟那个好(因为使用class选择器的关系)
BEN中的B是Block,指一个UI构成单元,E是Element,是B的后代,M是Modifer,表达状态,比如three、current、active等。
BEM使用连接符连接BEM,但不能使用相同的连接符连接BEM,例如使用
__
连接BE,使用_
连接EM,使用-
做连字符,比如header__item-list_active
第二个CSS选择器是速度快的,它只使用了BEM规则的类选择器,不仅性能最好,而且可读性良好。
选择器性能优化有时候不如良好的布局导致的节点数减少带来的性能提升更高。
当在一个循环中,先访问一些尺寸、位置等会导致Layout的属性,然后再改变尺寸,重新计算样式计算,会导致强制布局,当反复如此,会出现布局抖动。
应当尽量避免强制布局,在循环外读取属性,在循环内改变尺寸,不会造成强制布局。
可以使用Chrome的分析工具来分析页面的绘制过程,但是课程中的Show paint rectangles
已经不再原来的位置了
为了分析绘制过程,需要在开发者的工具的More tools
里面找到Rendering
,在打开的Rendering
面板中勾选Paint flasing
选项:
勾选之后,它会告诉你绘制流程在页面上的什么地方发生了,何时发生了。当页面有元素被绘制时,对应的元素会显示为绿色:
还可以使用Paint Profiler来确定页面的那些区域被绘制了,何时绘制的。但是新版本的Chomre开启Paint很麻烦。首先勾选Enable advanced paint instruments(slow)
然后点击Record进行录制,录制结束后找到绿色的Paint块并点击:
点击之后再下方出现了Paint Profiler
的选项:
可以根据左侧的命令结合上方的新的时间线和右侧的绘制结果,查看每一刻的绘制情况和绘制命令。
绘制之后的流程就是合成,就是将多个图层合并成为一个,当改变一个图层时,不会引起其他图层的绘制。
看下面的练习题,左侧是一个导航栏,那些元素应该放在一个图层上?
一起移动的元素,应该放在一个图层上。
Chrome的性能分析记录中有两个与图层合成有关,一个是Update Layer Tree
。当Chrome的引擎需要知道页面的哪个图层时就会出现该记录,查看元素的样式,弄清楚需要多少图层,另一个是Compositie Layers
,浏览器将页面合成到一起发送给屏幕。
图层越多,图层管理和合成花费的时间就越多,所以需要在减少绘制时间和增加图层管理时间二者之间做出权衡。
图层管理大多数情况下是浏览器自动完成的,但是当遇到绘制问题时,可以考虑将某个元素单独放到一个图层中。在创建图层之前,应该看一下看元素是否已经有了自己的图层,在刚才的Rendering
的功能面板在宏,勾选上Layers borders
选项
勾选之后,页面上除了绿色的框框,还会出现褐色的框框,绿色框框是浏览器对图层的划分,我们没有办法控制,橘色的框框表示元素位于自己的合成图层上。
如何创建属于自己的图层呢?
一般来说有两种方式:
will-change
,使用will-change
,属性值可以是transform
、left
、top
等外观属性,浏览器会根据这些提示为这些属性进行布局和绘制流程,由于浏览器创建图层也是要耗费性能的,使用will-change
最大的好处就是避免浏览器匆忙创建图层带来的性能代价。transform: translateZ(0)
或者transform: translate3D(0, 0, 0)
这两种方式是在为静止的元素(不改变其原始位置)创建单独图层,实际上还有一些其他的方式也会创建单独的图层,比如:
transform
的对应移动,transalte
、rotate
、scale
等video
/canvas
/iframe
等元素opacity
改变position: fixed
filter
overflow
不为visible
(隐式合成)Chrome创建层的标准是什么呢?完整的标准:
What else gets its own layer? Chrome’s heuristics here have evolved over time and continue to, but currently any of the following trigger layer creation:
- 3D or perspective transform CSS properties
elements using accelerated video decoding
elements with a 3D (WebGL) context or accelerated 2D context
- Composited plugins (i.e. Flash)
- Elements with CSS animation for their opacity or using an animated transform
- Elements with accelerated CSS filters
- Element has a descendant that has a compositing layer (in other words if the element has a child element that’s in its own layer)
- Element has a sibling with a lower z-index which has a compositing layer (in other words the it’s rendered on top of a composited layer)
可以参考这篇文章,讲得不错。
可以使用Layesr
工具来查看页面中有多少个图层:
注意要避免隐式提升,下图中的totes promited
成为单独图层的原因就是它覆盖在了于具有单独图层的元素的上方
所有元素都提升为单独的图层会消耗大量内存,并花费很多时间,在移动设备上问题更加明显。所以将元素提升到图层上时一定要谨慎,因为有可能会不小心由于存在重叠而创建了大量的其他的图层。
如何提升性能:
(1)JS
requestAnimation
来代替setTimeout
/setInterval
创建动画(2)CSS
will-change
提示浏览器将要发生的变化,并且创建单独的图层transform
和opacity
来创建单独的图层,实现动画分析工具:
Performance
Rendering
Laryouts