- 基本步骤
- 清空 canvas:在绘制每一帧动画之前,需要清空所有。清空所有最简单的做法就是 clearRect() 方法
- 保存 canvas 状态 如果在绘制的过程中会更改 canvas 的状态(颜色、移动了坐标原点等),又在绘制每一帧时都是原始状态的话,则最好保存下 canvas 的状态
- 绘制动画图形这一步才是真正的绘制动画帧
- 恢复 canvas 状态如果你前面保存了 canvas 状态,则应该在绘制完成一帧之后恢复 canvas 状态
- 控制动画
- 通过 canvas 的方法或者自定义的方法把图像绘制到 canvas 上
- 正常情况,我们能看到绘制的结果是在脚本执行结束之后。例如,我们不可能在一个 for 循环内部完成动画
- 为了执行动画,我们需要一些可以定时执行重绘的方法
- setInterval()
- setTimeout()
- `requestAnimationFrame()`
- setTimeout和setInterval的问题是,它们都不精确。它们的内在运行机制决定了时间间隔参数实际上只是指定了把动画代码添加到浏览器UI线程队列中以等待执行的时间。如果队列前面已经加入了其他任务,那动画代码就要等前面的任务完成后再执行
- requestAnimationFrame会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率
- 在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的CPU、GPU和内存使用量
- requestAnimationFrame是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销
- requestAnimationFrame的用法与settimeout很相似,只是不需要设置时间间隔而已
- requestAnimationFrame使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用。它返回一个整数,表示定时器的编号,这个值可以传递给cancelAnimationFrame用于取消这个函数的执行
- 回调函数有一个参数,是一个相对的时间毫秒值,表示当前的刷新时间
window.requestAnimationFrame(function(time){ //显示屏刷新的时候被执行 });
- 兼容写法
if(!window.requestAnimationFrame){ var lastTime = 0; window.requestAnimationFrame = function(callback){ var currTime = new Date().getTime(); var timeToCall = Math.max(0,16.67-(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); }; } var timer; btn.onclick = function(){ myDiv.style.width = '0'; cancelAnimationFrame(timer); timer = requestAnimationFrame(function fn(){ if(parseInt(myDiv.style.width) < 500){ myDiv.style.width = parseInt(myDiv.style.width) + 5 + 'px'; myDiv.innerHTML = parseInt(myDiv.style.width)/5 + '%'; timer = requestAnimationFrame(fn); }else{ cancelAnimationFrame(timer); } }); }- requestAnimationFrame 不管理回调函数队列,而滚动、触摸这类高触发频率事件的回调可能会在同一帧内触发多次, 所以在同一帧内可能调用多次 requestAnimationFrame 时,要管理回调函数,防止重复绘制动画
- 一般的解决方法是使用节流函数。但是在这里使用节流函数并不能完美解决问题。因为节流函数是通过时间管理队列的,而 requestAnimationFrame 的触发时间是不固定的,在高刷新频率的显示屏上时间会小于 16.67ms,页面如果被推入后台,时间可能大于 16.67ms, 完美的解决方法是通过 requestAnimationFrame 来管理队列
// 原理示意 const onScroll = e => { if (scheduledAnimationFrame) { return } scheduledAnimationFrame = true window.requestAnimationFrame(timestamp => { scheduledAnimationFrame = false animation(timestamp) }) } window.addEventListener('scroll', onScroll)
// 正式解决方案 const callbackList = [] fun..."