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

注意,注意

你千万不要反过来分析,先分析小时数,那样会累的很死,尤其是数学思维不敏捷的

Canvas 基础教程

关于   |   FAQ   |   我们的愿景   |   广告投放   |  博客

  简单教程,简单编程 - IT 入门首选站

Copyright © 2013-2022 简单教程 twle.cn All Rights Reserved.