页面渲染过程
为了使每一帧页面渲染的开销都能在期望的时间范围内完成。就需要开发者了解渲染过程的每个阶段,以及各阶段中有哪些优化空间是我们力所能及的。经过分析根据开发者对优化渲染过程的控制力度,可以大体将其划分为5各部分:js处理、计算样式、页面布局、绘制与合成。这个过程中的每一个阶段都有可能产生卡顿。注意:并非对于每一帧画面都会经历这5个部分,比如仅修改与绘制相关的属性(文字颜色,背景图片或边缘阴影等),而未对页面布局产生任何修改,那么在计算样式阶段完成后,便会跳过页面布局直接执行绘制。如果所更改的属性既不影响页面布局又不需要重新绘制,便可直接跳到合成阶段执行。具体修改哪些属性会触发页面布局、绘制或合成阶段的执行,这与浏览器的内核存在一定关系。
Google的chrome实验室在网站上列出了许多css属性的详细表现,可以自行查看。
js执行优化
实现动画效果
推荐使用requestAnimationFrame方法来实现动画效果,requestAnimationFrame方法的执行时机会与系统的刷新频率同步。这样就能保证回调函数在屏幕的每次刷新间隔中只被执行一次,从而避免因随机丢帧而造成的卡顿现象。
其使用方法也十分简单,仅接受一个回调函数作为入参,即下次重绘之前更新动画帧所调用的函数。返回值为一个long类型整数,作为回调任务队列中的唯一标识,可将该值传给window.cancelAnimationFrame来取消回调,以某个目标元素的平移动画为例:
除了通过让回调函数的触发时机与系统刷新频率同步来消除动画的丢帧卡顿,requestAnimationFrame方法还能通过节流不必要的函数执行,来帮助cpu的节能。
具体而言,对于cpu节能方面,考虑当浏览器页面最小化或被隐藏起来时,动画对用户来说是不可见的,那么刷新动画所带来的页面渲染就是对cpu资源的浪费,完全没有意义。
当创建setInterval定时器后,除非显式调用clearInterval去销毁该定时器,不然在后台的动画任务会不断执行,而requestAnimationFrame方法则完全不同,当页面未被激活时,屏幕刷新任务会被系统暂停,只有当页面被激活时,动画任务才会被激活并从上次暂停的地方继续执行,所以能有效地节省cpu开销。
在页面地一些高频事件中,比如页面滚动的scroll、页面尺寸更改的resize,需要防止在一个刷新时间间隔内发生多次函数执行,也就是所谓的函数节流。对60hz的显示器来说,差不多每16.7ms刷新一次,多次绘制并不会在屏幕上体现出来,所以requestAnimationFrame方法仅在每次刷新周期中执行一次函数调用,既能保证动画的流畅性又能很好地节省函数执行地冗余开销。
恰当使用web worker
js是单线程执行的,所有任务放在一个线程上执行,只有当前一个任务执行完才能处理后一个任务,不然后面的任务只能等待,这就限制了多核计算机充分发挥它的计算能力。同时在浏览器上,js的执行通常位于主线程,这恰好与样式计算、页面布局以及绘制一起,如果js运行时间过长,必然就会导致其他工作任务的阻塞而造成丢帧。
为此可将一些纯计算的工作迁移到web worker上处理,它为js的执行提供了多线程环境,主线程通过创建出worker子线程,可以分担一部分自己的任务执行压力。在worker子线程上执行的任务不会干扰主线程,待其上的任务执行完成后,会把结果返回给主线程,这样的好处是让主线程可以更专注地处理ui交互,保证页面的使用体验流程。需要注意的是,worker子线程一旦创建成功就会始终执行,不会被主线程上的事件所打断,这就意味着worker会比较耗费资源,所以不应当过度使用,一旦任务执行完毕就应及时关闭。除此之外,在使用中还有以下几点应当注意。