浏览器帧
这个看了一堆文档越看越糊涂,但还是做一个总结吧,后期会及时修正。
为什么会丢帧呢?
浏览器根据 VSync 垂直同步信号(verticalsynchronization) 来进行渲染,浏览器渲染引擎渲染和这个信号保持一致才不会产生页面卡顿问题,也就是按照页面60mhz的刷新率一个 VSync 时钟周期大多是 16.66 (1000/60) ms。在这16.66ms的时间内浏览器需要处理完成一帧就可以,所以优化点在于如何保证需要处理的任务和渲染在16.6ms中完成,而不至于因为js引擎执行时间过长阻塞GUI渲染。
这里特指Javascript 引擎是单线程运行的。 严格来说,Javascript 引擎和页面渲染引擎在同一个渲染线程,GUI 渲染和 Javascript执行 两者是互斥的. 另外异步 I/O 操作底层实际上可能是多线程的在驱动。
requestAnimationFrame在没有耗时任务的情况下会在每一帧开始执行,属于可以将高优先级任务放置到其中。但是回调的调用可能受到js引擎执行时间过长而延迟。
浏览器绘制是按照一秒60帧来的,但是也会因为性能而降低帧率。
事件循环队列:script(整体代码)、setTimeout、setInterval、UI 渲染、 I/O、postMessage、 MessageChannel、setImmediate(Node.js 环境)
任务队列:Promise、 MutaionObserver、process.nextTick(Node.js环境)
以下是来自google developers的一张图片
这个是在一帧中需要执行的管道流程。(详情查看 google developer)
1、开始新的一帧,js引擎开始执行任务,这个任务包括页面点击,滚动事件,定时器事件等。(滚动事件如果不注册handler性能会更高,因为不需要经过js引擎渲染而是直接提交绘制)
2、执行完成事件循环队列以及任务队列之后,执行requestAnimationFrame,如果js引擎执行时间不长的话,requestAnimationFrame会在每一帧的渲染开始前触发,如果js引擎执行事件循环时间过长requestAnimationFrame会在js引擎执行完成事件循环队列和任务队列之后多次执行。
3、回流
触发条件: 如果您修改元素的“layout”属性,也就是改变了元素的几何属性(例如宽度、高度、左侧或顶部位置等),那么浏览器将必须检查所有其他元素,然后“自动重排”页面。任何受影响的部分都需要重新绘制,而且最终绘制的元素需进行合成。
解析html(在页面加载过程中,或者进行 appendChild 等dom操作后),解析css(修改类名,style等样式的时候)。
如果没有做以上操作则这一步会跳过,就是直接从第2步进入第4步
4、重绘
触发条件: 如果您修改“paint only”属性(例如背景图片、文字颜色或阴影等),即不会影响页面布局的属性,则浏览器会跳过布局,但仍将执行绘制。但是一旦回流肯定会引发重绘。
5、合成
如果您更改一个既不要布局也不要绘制的属性,则浏览器将跳到只执行合成,不过如果上面的都执行了这一步也是必然执行的。
那么如何做到更改一个元素直接进行合成呢?从回流和重绘的触发条件可以看出,如果一个css属性不改变元素的宽高位置,也不改变背景图片文字颜色和阴影,那么其实就可以直接进入合成阶段,满足这个条件的css属性如下:
transform不会脱离文档流,也不改变文档流的大小和位置,所以不会引发回流和重绘。
1、js任务执行时间过长
2、触发了强制同步布局,也就是在垂直同步信号触发渲染之前在修改了元素的宽高之后又重新获取导致了渲染布局提前了。
应用
因为浏览器每一帧的绘制都是16ms左右,所以可能存在空闲的时间,如何将一个复杂的任务分解在这些空闲的时间里,就是时间分片技术。
实现的前提是:1、任务要分割的尽可能小,以免耽误下一次的渲染。2、任务具有完成运行性,而不是中断运行。
渲染优化:使用定时器驱动动画和使用requestAnimationFrame驱动动画的区别。
1、定时器运作机制:setTimeout最小运行间隔是4ms这个间隔和渲染帧的运行时间并不一致,会导致动画在一帧内多次改变然后再渲染从而掉帧,但是使用requestAnimationFrame是在每一帧渲染之前执行,所以可以保证每一次回调执行结束都伴随着渲染。