使用 requestAnimationFrame 实现动画
如何实现一个动画
我们来实现一个最简单的需求,将一个元素从屏幕左边均匀地移动到屏幕右边。
(1)css animation
用 css 实现是最合理也是最高效的。
@keyframes move_animation1 {
0% { left: 0px; }
100% { left: calc(100% - 60px); }
}
@keyframes move_animation {
0% { transform: translateX(0); }
50% { transform: translateX(250px); }
100% { transform: translateX(500px)); }
}
.animate-div {
width: 60px;
height: 40px;
border-radius: 5px;
background: #92B901;
left: 0;
position: absolute;
transform: translateZ(0);
-webkit-transform: translateZ(0);
animation: move_animation 5s linear 2s infinite alternate;
}
注:
transform:translateZ(0);
用来开启 chrome GPU 加速,解决动画”卡顿”。 在动画中使用 transform 比 left/top 性能更好,能减少浏览器 repaint。
(2)假如用 JS 实现呢
首先想到的是 setInterval/setTimeout,原理就是利用人眼的视觉残留和电脑屏幕的刷新,让元素以连贯平滑的方式逐步改变位置,最终实现动画的效果。
常用的屏幕刷新频率为 60Hz,一些电竞屏幕则为 144Hz。我们以常用的刷新频率为例,60Hz 意味着屏幕每 1000 / 60 ≈ 16.7ms 刷新一次,所以我们设置 setInterval 的间隔为 16.7ms:
const animateDiv = document.querySelector('.animate-div')
let i = 0
let inter = setInterval(() => {
animateDiv.style.left = 1/3 * (++i) + '%'
if (i === 300) clearInterval(inter)
}, 16.7)
setInterval/setTimeout 存在两个问题:
- setTimeout 的执行时间并不是确定的。在 Javascript 中, setTimeout 任务被放进了异步队列中,只有当主线程上的任务执行完以后,才会去检查该队列里的任务是否需要开始执行,因此 setTimeout 的实际执行时间一般要比其设定的时间晚一些。
- 刷新频率受屏幕分辨率和屏幕尺寸的影响,因此不同设备的屏幕刷新频率可能会不同,而 setTimeout 只能设置一个固定的时间间隔,这个时间不一定和屏幕的刷新时间相同。
以上两种情况都会导致 setTimeout 的执行步调和屏幕的刷新步调不一致,从而引起丢帧现象。 虽然在上述代码中我们将时间间隔设置为 16.7ms,但是还是不能完全避免丢帧的现象。
(3)requestAnimationFrame
requestAnimationFrame 与 setTimeout/setInterval 最大的区别是由系统自己的刷新机制来决定什么时候调用动画函数,开发者只需要定义好动画函数,这个函数会在浏览器重绘之前调用。
requestAnimationFrame 简介
requestAnimationFrame 接收一个回调函数作为参数,DOMHighResTimeStamp,指示当前被 requestAnimationFrame() 排序的回调函数被触发的时间。回调函数中传入时间戳作为参数,该时间戳是一个十进制数,单位毫秒,最小精度为 1ms。
const animateDiv = document.querySelector('.animate-div')
let start = null
// 回调函数
function step(timestamp) {
if (!start) start = timestamp
let progress = timestamp - start
animateDiv.style.left = progress + 'px'
if (progress < 350) {
// 在动画没有结束前,递归渲染
window.requestAnimationFrame(step)
}
}
// 第一帧渲染
window.requestAnimationFrame(step)
requestAnimationFrame 优势
除了精准控制调用时机以外,requestAnimationFrame 还有两大优点:
- 运行在后台标签页或者隐藏的 iframe 里时,requestAnimationFrame() 暂停调用以提升性能和电池寿命。
- 函数节流:在高频率事件(resize,scroll等)中,为了防止在一个刷新间隔内发生多次函数执行,使用 requestAnimationFrame 可保证每个刷新间隔内,函数只被执行一次。
cancelAnimationFrame
取消一个先前通过调用 window.requestAnimationFrame()方法返回的动画帧请求。
const animateDiv = document.querySelector('.animate-div')
const requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame || window.msRequestAnimationFrame
const cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame
let start = null
let myReq = null
function step(timestamp) {
let progress = timestamp - start
animateDiv.style.left = progress + 'px'
if (progress < 2000) {
myReq = requestAnimationFrame(step)
}
}
myReq = requestAnimationFrame(step)
setTimeout(() => {
window.cancelAnimationFrame(myReq)
}, 200)
优雅降级
requestAnimationFrame 目前还存在兼容性问题,使用 requestAnimationFrame polyfill 来进行优雅降级。
if (!Date.now)
Date.now = function() { return new Date().getTime(); };
(function() {
'use strict';
let vendors = ['webkit', 'moz'];
for (let i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) {
let vp = vendors[i];
window.requestAnimationFrame = window[vp+'RequestAnimationFrame'];
window.cancelAnimationFrame = (window[vp+'CancelAnimationFrame']
|| window[vp+'CancelRequestAnimationFrame']);
}
if (/iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent) // iOS6 is buggy
|| !window.requestAnimationFrame || !window.cancelAnimationFrame) {
let lastTime = 0;
window.requestAnimationFrame = function(callback) {
let now = Date.now();
let nextTime = Math.max(lastTime + 16, now);
return setTimeout(function() { callback(lastTime = nextTime); },
nextTime - now);
};
window.cancelAnimationFrame = clearTimeout;
}
}());
参考资料
相关文章
- Python集成开发环境Codimension 2.1.1发布
- Python程序的执行原理
- Slashdot对Python之父的采访
- 优化C++代码(2):C++代码的编译过程
- 数据处理性能对比(Python原生vs Pandas vs Numpy)
- 基于Python的Grib数据可视化
- C语言在2013年仍很重要
- 12岁的少年教你用Python做小游戏
- python:常用功能之文本处理
- Python六大开源框架对比:Web2py略胜一筹
- 从银行转账失败到分布式事务:总结与思考
- Python如何使基于Java的StubHub受益
- 再谈PHP、Python与Ruby
- Java 8中时间API初探之一
- R vs Python,数据分析中谁与争锋?
- Python 新手常犯错误(第一部分)
- 如何使用Python对Instagram进行数据分析?
- 我是如何从会计转行到数据分析
- 10行Python代码创建可视化地图
- 五大理由从Python转到Go语言