Canvas requestAnimationFrame()
上一章节的末尾,我们使用 setInterval()
函数实现模拟了一个小小的太阳系系统,这个函数啊,怎么说呢
如果你对 JavaScript 有深入了解,就会知道它的执行时间不是那么的准确
<p id="out"></p> <script> var startTime = new Date().getTime(); var now = 0; var count = 0; var t = setInterval(function(){ count++; if ( count > 10 ) { clearInterval(t); } now = new Date().getTime() document.getElementById("out").innerHTML += now - startTime + "<br/>"; startTime = now; }, 1000/60); </script>
运行结果如下
可以看到,执行的间隔时间根本不是 1000/60=16.666666666666668
为什么会这样? 因为 setInterval()
只是告诉系统我要每隔 16ms
执行下,但系统未必真的照办,而是在有空闲的时候才来执行一下
为了解决这种执行时间不准确的问题,现在的所有浏览器都提供了 requestAnimationFrame()
函数
window.requestAnimationFrame() 函数
window.requestAnimationFrame()
的用法与 setTimeout()
很相似,只是不需要设置时间间隔而已
requestAnimationFrame()
会定时的执行一个回调函数,这个时间大概是 60次/s
也就是说大概每秒 60 帧这样子
为什么是 60 ,因为主流的液晶显示器的刷新频率就是
60Hz
语法
window.requestAnimationFrame(callback);
参数 | 说明 |
---|---|
callback | 回调函数,这个回调函数只有一个参数,DOMHighResTimeStamp ,指示 requestAnimationFrame() 开始触发回调函数的当前时间 ( performance.now() 返回的时间) |
返回值
一个 long 整数,请求 id ,是回调列表中唯一的标识
我们可以通过把这个 id 传递给 window.cancelAnimationFrame()
以取消回调函数
window.cancelAnimationFrame() 函数
window.cancelAnimationFrame()
函数用于取消 window.requestAnimationFrame()
方法添加到计划中的动画帧请求
语法
window.cancelAnimationFrame()(requestID);
参数 | 说明 |
---|---|
requestID | 调用 window.requestAnimationFrame() 方法时返回的 ID |
范例
我们看看 window.requestAnimationFrame()
执行之间的间隔如何
<p id="out2" style="height:300px"></p> <script> var cur = 0; var cnt = 0; var rq = null; window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; window.cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame || window.webkitCancelAnimationFrame || window.msCancelAnimationFrame; function step(tm) { if ( cnt > 10 ) { window.cancelAnimationFrame(rq); return; } cur = tm; if ( cnt > 0 ) { document.getElementById("out2").innerHTML += (cur - st) + "<br/>"; } st = cur; rq = requestAnimationFrame(step); cnt++; } rq = requestAnimationFrame(step); </script>
运行输出如下
已经很准确了,虽然,偶尔有点神经质的大了好多,但绝对不会小
范例 2
我们使用 window.requestAnimationFrame()
函数来实现一个时钟
这个例子实现一个动态时钟, 可以显示当前时间。
<!DOCTYPE html> <meta charset="utf-8"> <canvas id="canvas-1" width="400" height="300"> </canvas> <script> function clock(){ var now = new Date(); var ctx = document.getElementById('canvas').getContext('2d'); ctx.save(); ctx.clearRect(0,0,150,150); ctx.translate(75,75); // 表盘 ctx.scale(0.4,0.4); ctx.rotate(-Math.PI/2); ctx.strokeStyle = "black"; ctx.fillStyle = "white"; ctx.lineWidth = 8; ctx.lineCap = "round"; // 时针 ctx.save(); for (var i=0;i<12;i++){ ctx.beginPath(); ctx.rotate(Math.PI/6); ctx.moveTo(100,0); ctx.lineTo(120,0); ctx.stroke(); } ctx.restore(); // 分针 ctx.save(); ctx.lineWidth = 5; for (i=0;i<60;i++){ if (i%5!=0) { ctx.beginPath(); ctx.moveTo(117,0); ctx.lineTo(120,0); ctx.stroke(); } ctx.rotate(Math.PI/30); } ctx.restore(); var sec = now.getSeconds(); var min = now.getMinutes(); var hr = now.getHours(); hr = hr>=12 ? hr-12 : hr; ctx.fillStyle = "black"; // 秒针 ctx.save(); ctx.rotate( hr*(Math.PI/6) + (Math.PI/360)*min + (Math.PI/21600)*sec ) ctx.lineWidth = 14; ctx.beginPath(); ctx.moveTo(-20,0); ctx.lineTo(80,0); ctx.stroke(); ctx.restore(); // 分针时间 ctx.save(); ctx.rotate( (Math.PI/30)*min + (Math.PI/1800)*sec ) ctx.lineWidth = 10; ctx.beginPath(); ctx.moveTo(-28,0); ctx.lineTo(112,0); ctx.stroke(); ctx.restore(); // 秒针时间 ctx.save(); ctx.rotate(sec * Math.PI/30); ctx.strokeStyle = "#D40000"; ctx.fillStyle = "#D40000"; ctx.lineWidth = 6; ctx.beginPath(); ctx.moveTo(-30,0); ctx.lineTo(83,0); ctx.stroke(); ctx.beginPath(); ctx.arc(0,0,10,0,Math.PI*2,true); ctx.fill(); ctx.beginPath(); ctx.arc(95,0,10,0,Math.PI*2,true); ctx.stroke(); ctx.fillStyle = "rgba(0,0,0,0)"; ctx.arc(0,0,3,0,Math.PI*2,true); ctx.fill(); ctx.restore(); ctx.beginPath(); ctx.lineWidth = 14; ctx.strokeStyle = '#325FA2'; ctx.arc(0,0,142,0,Math.PI*2,true); ctx.stroke(); ctx.restore(); window.requestAnimationFrame(clock); } window.requestAnimationFrame(clock); </script>
整个代码都很简单,我解释下下面三行代码
// 时针 ctx.rotate( hr*(Math.PI/6) + (Math.PI/360)*min + (Math.PI/21600)*sec ) // 分针 ctx.rotate( (Math.PI/30)*min + (Math.PI/1800)*sec ) // 秒针 ctx.rotate(sec * Math.PI/30);
秒针
对于秒针,因为 1 分 = 60 秒
,而表盘有 60
刻度,也就是一个刻度一秒,那么当前秒针旋转位置就很简单了,直接是
sec * (2π/60)= sec * π / 30
分针
对于分针,同样 1 小时 = 60 分
,当前时间分钟数贡献的秒针位置应该是
min * (2π/60)= min * π / 30
但是,因为秒针也会贡献分针的旋转弧度,这个弧度是多少呢?
1 小时 = 3600 秒
,那么秒针贡献的弧度就是
sec * (2π/3600) = sex * π / 1800;
于是分针的旋转弧度就是
min * π / 30 + sex * π / 1800
时针
时针也是同样的道理,表盘只有 12 个小时,那么当前时间的小时数贡献的弧度就是
hour * (2π/12) = hour * π / 6
当前时间的分钟数也会贡献弧度啊,它是怎么贡献的?大家心里默默计算吧,分针跑一圈才贡献了 1 小时,那么它跑满 12 个小时要贡献多少分钟呢 12 * 60 = 720
对吧
所以它每跑一个刻度,才贡献给时针 2π/(12 * 60) = π/360
那么分钟数贡献给时针的弧度就是
min * π / 360
如果说分针已经是跑的满头大汗了,那么秒针估计要跑到虚脱,为什么呢? 秒针跑满 12 个小时要多少秒 ?
12 * 60 * 60 = 12 * 3600 = 43200
也就是说秒针每跑一个刻度贡献给时针的弧度才 2π/43200= π / 21600
真的是望山跑死马,所以秒数贡献给时针的弧度是
sec * π / 21600
把上面三着相加等于
hour * π / 6 + min * π / 360 + sec * π / 21600
注意,注意
你千万不要反过来分析,先分析小时数,那样会累的很死,尤其是数学思维不敏捷的