这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战
目录
- 浏览器渲染过程
- requestAnimationFrame
- 应用案例:进度条&文字跑马灯
- 浏览器兼容性
1. 浏览器渲染过程
浏览器通过http请求获取到资源之后,会将HTML解析成一棵DOM树,并根据CSS构建一棵CSSOM树,然后将DOM树和CSSOM树结合,组合成一棵Render树;再根据Render树进行布局(layout),即确定每个树节点的宽、高、位置及元素之间的位置关系(第一次确定节点的大小和位置称为布局。随后对节点大小和位置的重新计算称为重排(reflow),也有些文章称之为回流。)。布局完成后,浏览器就可以在屏幕上进行像素绘制。
当Render树中的节点样式发生改变时,如果是节点的宽、高或位置等,会引起布局改变的变化,那么就需要进行重排,重排之后必然引起重绘(注:重绘不一定会引起重排)。
大部分浏览器在一秒钟内可以显示的最大帧数是60(刷新率FPS=60Hz)。也就是说,无论你每秒渲染多少帧,一秒钟内最多只有 60 帧会出现在屏幕上。两帧之间的最大间隔时长是1000/60ms,如下图所示,浏览器会在两次刷新间隔里占用部分或全部时间去做渲染。
当浏览器渲染某一帧的时长超过了1000/60ms会发生什么?
如上图所示,因为第二帧Frame2的时长超过了1000/60ms,而挤占了原本第三帧Frame3的时间,而浏览器也不会将第三帧延迟渲染,而是直接跳过第三帧,继续渲染第四帧Frame4。这就是所谓的“掉帧”。
2. requestAnimationFrame
以往的JS动画通常是用setTimeout或setInterval定时器来实现,这种实现方法存在的问题,一是上面提到的“掉帧”,二是无法掌握回调函数的执行时机,三是系统性能的浪费,当页面转为后台运行时并不会自动停止。
requestAnimationFrame则解决了定时器的这些问题。当然,requestAnimationFrame并非完美,因为是在主线程上执行的,当主线程非常繁忙时,requestAnimationFrame的效果就大打折扣。
3. 应用案例
3.1 进度条
先看下效果图:
核心代码:
function stepper() {
window.requestAnimationFrame(() => {
if (timer <= max) {
// 进度条长度设置
progresser.style.width = timer + "%";
// 展示百分比数值
label.innerText = timer + "%";
// 更新数值文本的位置
label.style.left =
timer - (label.offsetWidth / bar.offsetWidth) * 100.0 + "%";
timer++;
// 当进度条未达max值,则下一帧继续渲染动画
stepper();
}
});
}
完整代码在线调试:jsrun.net/zx8Kp/edit
3.2 文字跑马灯
效果图:
核心代码:
function step() {
// 当文字位移小于容器长度,继续向右移动
if (left <= length) {
text.style.left = left + "px";
text.style.width = width + "px";
left += move;
} else {
// 当文字位移超出容器长度,重新从左边开始移动
left = -width;
text.style.left = left + "px";
text.style.width = width + "px";
}
// 无限循环跑马灯动画
window.requestAnimationFrame(step);
}
完整代码在线调试:jsrun.net/Px8Kp/edit
4. 浏览器兼容性
目前大部分浏览器都已实现window.requestAnimationFrame:
对于未实现的window.requestAnimationFrame的浏览器也有开源的polyfill可供使用,如rAF.js(gist.github.com/paulirish/1…),代码不到30行:
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame']
|| window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());
参考资料:
[1]渲染页面-浏览器工作原理:developer.mozilla.org/zh-CN/docs/…
[2]window.requestAnimationFrame:developer.mozilla.org/zh-CN/docs/…
[3]rAF.js:gist.github.com/paulirish/1…