引言
随着硬件设备性能的不断提升,越来越多的设计师在页面设计上增加交互效果来提升产品的用户体验。因此页面中出现越来越多的交互效果,例如页面转场、下拉刷新、状态切换等更复杂的动画效果。以往我们关注的性能主要在服务端响应、文档下载、文档渲染三个阶段,性能优化大部分也集中在这三个阶段。现在我们也需要去关注页面加载完成后的操作体验,页面的流畅度。
性能衡量标准
如何评价页面的流畅度需要一个衡量的标准。大多数显示器的刷新率为60Hz,因此60fps就是一个理想的状态。越接近60fps,页面上动画的流畅度越完美。想要获得柔丝般顺滑的动画就需要在 1 / 60FPS(约等于16.7ms)内,把一帧绘制完成。因此需要解决两个问题
- 绘制时机
- 绘制时长
绘制时机
如何保证绘制时机,首先会想到使用 setTimeout 和 setInterval 来处理。
setTimeout(callback, 1000 / 60); setInterval(callback, 1000 / 60);
但是使用 setTimeout 和 setInterval 会存在一些问题。
- 定时器计算延时的精确度不够,延时的计算依靠的是浏览器的内置时钟,而时钟的精确度又取决于时钟更新的频率,现代浏览器时钟更新的频率为4ms。setTimeout的延迟设置为16.7ms,那么要更新20ms(5 * 4ms)才会触发延迟,会比设置的延迟多延迟3.3ms。
- 在定时器等待延迟触发的过程中,常常会有新的同步脚本需要执行,就会延后回调执行的时间,如果使用setInterval会丢弃任务。
- 如果一个动画有多个setTimeout控制,多个setTimeout不会进行合并。动画的不同部分渲染的时机不一致。
- 不是所有的设备刷新率都为60Hz,因此setTimeout延迟设置为 1000 / 60 并不准确,可能过大或过小。过小会浪费CPU周期以及消耗额外的电能,过大会流畅度有所下降。
setTimeout延迟设置过小,不但会浪费CPU周期以及消耗额外的电能, 也会出现掉帧的问题。
var i = 0; var colors = ['red', 'yellow', 'blue']; // var fps = 1000 / 60; var fps = 1000 / 100; function update() { $main.style.background = colors[i%3]; i += 1; } function loop() { setTimeout(function() { update(); loop(); }, fps); } loop();
fps = 1000 / 60;
fps = 1000 / 100;
如何解决绘制时机的问题?
Mozilla的Robert O’Callahan CSS 动画的优势在于浏览器知道动画什么时候开始,因此会计算出正确的循环间隔,在适当的时候刷新 UI。而对于 JavaScript 动画,浏览器就无从知晓什么时候开始。因此创建一个新的回调函数mozRequestAnimationFrame,浏览器在更新动画是触发回调函数。
requestAnimationFrame 解决定时器函数的不足。
- 只有当页面真正开始渲染时,回调函数才会被调用。
- 当页面不可见时 requestAnimationFrame 的回调不会执行,但定时器回调依旧在执行。
定时器(setTimeout、setInterval)
requestAnimationFrame
绘制时长
绘制时机的问题解决了,然后就需要解决绘制时长的问题。
页面渲染大致流程如下:
在流程中比较关心的是 Layout、Paint、Composite,排在越左边的步骤执行的耗时就越长。尽可能的减少渲染或使用低消耗渲染代替高消耗的渲染。
触发Layout
- 改变width, height, margin等和大小、位置相关的属性
- 读取元素的size, position相关属性
- 元素 focus,scroll 等
https://gist.github.com/paulirish/5d52fb081b3570c81e3a 基本统计了会触发Layout/reflow的行为,还有对应的chrome源码。
如何避免Layout
1、当修改元素的位置(top、left、right、bottom),使用transform来代替。
var $container = document.querySelector('.container'); var i = 0; setInterval(function() { // $container.style.left = (i%200) + 'px'; $container.style.transform = 'translate3d(' + (i%200) + 'px,0,0)'; i++; }, 100);
更新left属性
更新transform属性
2、通过变量预先缓存元素的样式,避免每次重新获取
Paint
触发Paint
当修改border-radius, box-shadow, color等展示相关属性时,会触发paint。
避免Paint
通过chrome的工具来辅助,减少重复的Paint、Patint的区域。
Composite
合成是将页面的已绘制部分放在一起以在屏幕上显示的过程。
Composite 是渲染过程中消耗最小,对 transform、opacity 改变只会触发Composite,最好使用transform 和 opacity 来实现动画。
后记
本文仅是管中窥豹,很多细节没有深入展开。更多的了解页面渲染流程及细节,熟练运用调试工具,在对页面体验进行性能优化时才能有的放矢。
本文来源于:前端性能优化——动画篇-变化吧门户
特别声明:以上文章内容仅代表作者本人观点,不代表变化吧门户观点或立场。如有关于作品内容、版权或其它问题请于作品发表后的30日内与变化吧联系。
- 赞助本站
- 微信扫一扫
-
- 加入Q群
- QQ扫一扫
-
评论