requestAnimationFrame它是请求动画帧,以浏览器的显示帧率来作为其动画动作的频率。比如浏览器每16.7ms刷新一次,动画回调也会每16.7ms调用一次
用法:requestAnimationFrame(callback);
该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行,回调函数执行次数通常与 浏览器屏幕刷新次数 相匹配。
终止执行
window.cancelAnimationFrame() 可以取消回调函数。 只需要把 requestAnimationFrame 的返回值作为参数传递给 cancelAnimationFrame 就可以了
requestAnimationFrame 与 setTimeout 、 setInterval用来做动画的优缺点
setTimeout 和 setInterval 的问题是,它们不够精确。它们的内在运行机制决定了 时间间隔参数 实际上只是指定了把动画代码添加到 浏览器UI线程队列 中以等待执行的时间。如果队列前面已经加入了其它任务,那动画代码就要等前面的 任务完成后 再执行,并且如果时间间隔过短(小于16.7ms)会造成丢帧,所以就会导致动画可能不会按照预设的去执行,降低用户体验。
requestAnimationFrame 采用 浏览器时间间隔 ,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,消耗性能;也不会因为间隔时间太长,使用动画卡顿不流畅,让各种网页动画效果能够有一个 统一 的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。
<div id="test"
style="width: 0px; height: 12px; line-height: 12px; margin-bottom: 5px; background: rgb(185, 236, 243);">
</div>
当前进度:<span id="progress">0%</span>
<button id="btn">开启</button>
const btn = document.getElementById('btn');
const test = document.getElementById('test');
//使用 requestAnimationFrame 实现
btn.onclick = function() {
var timer = requestAnimationFrame(function fn() {
if (parseInt(test.style.width) < 300) {
test.style.width = parseInt(test.style.width) + 3 + 'px';
progress.innerHTML = parseInt(test.style.width) / 3 + '%';
timer = requestAnimationFrame(fn);
} else {
cancelAnimationFrame(timer);
}
});
}
//使用 setInterval 实现
btn.onclick = function() {
var timer = setInterval(function() {
if (parseInt(test.style.width) < 300) {
test.style.width = parseInt(test.style.width) + 3 + 'px';
progress.innerHTML = parseInt(test.style.width) / 3 + '%';
} else {
clearInterval(timer);
}
}, 17);
}
//使用 setTimeout 实现
btn.onclick = function() {
var timer = setTimeout(function fn() {
if (parseInt(test.style.width) < 300) {
test.style.width = parseInt(test.style.width) + 3 + 'px';
progress.innerHTML = parseInt(test.style.width) / 3 + '%';
timer = setTimeout(fn, 17);
} else {
clearTimeout(timer);
}
}, 17);
}
浏览器渲染有个渲染时机(Rendering opportunity)的问题,也就是浏览器会根据当前的浏览上下文判断是否进行渲染,它会尽量高效,只有必要的时候才进行渲染,如果没有界面的改变,就不会渲染。按照规范里说的一样,因为考虑到硬件的刷新频率限制、页面性能以及页面是否存在后台等等因素,有可能执行完 setTimeout 这个 task 之后,发现还没到渲染时机,所以 setTimeout 回调了几次之后才进行渲染
requestAnimationFrame 的使用场景
在大数据渲染过程中,比如表格的渲染,如果不进行一些性能策略处理,就会出现 UI 冻结现象,用户体验极差。有个场景,将后台返回的十万条记录插入到表格中,如果一次性在循环中生成 DOM 元素,会导致页面卡顿5s左右。这时候我们就可以用 requestAnimationFrame 进行分步渲染,确定最好的时间间隔,使得页面加载过程中很流畅。
var total = 10000;
var size = 100;
var count = total / size;
var done = 0;
var ul = document.getElementById('list');
function addItems() {
var li = null;
var fg = document.createDocumentFragment();
for (var i = 0; i < size; i++) {
li = document.createElement('li');
li.innerText = 'item ' + (done * size + i);
fg.appendChild(li);
}
ul.appendChild(fg);
done++;
if (done < count) {
requestAnimationFrame(addItems);
}
};
requestAnimationFrame(addItems);
CSS 动画原理
根据上面的原理我们知道,你眼前所看到图像正在以每秒 60 次的频率绘制,由于频率很高,所以你感觉不到它在绘制。而 动画本质就是要让人眼看到图像被绘制而引起变化的视觉效果,这个变化要以连贯的、平滑的方式进行过渡。 那怎么样才能做到这种效果呢? 60Hz 的屏幕每 16.7ms 绘制一次,如果在屏幕每次绘制前,将元素的位置向左移动一个像素,即1px,这样一来,屏幕每次绘制出来的图像位置都比前一个要差1px,你就会看到图像在移动;而由于人眼的视觉停留效应,当前位置的图像停留在大脑的印象还没消失,紧接着图像又被移到了下一个位置,这样你所看到的效果就是,图像在流畅的移动。这就是视觉效果上形成的动画。