JavaScript动画基础:canvas绘制简单动画

1.定时循环操作的三个函数

      对于动画,需要在一段时间内渲染不同的帧,各帧间隔一定的时间在画布中依次被绘制。为完成定时循环操作帧,可以利用etInterval()、setTimeout()和requestAnimationFrame()这三个函数之一。

      (1)setTimeout()方法。

      setTimeout() 方法是HTML DOM Window对象的一个方法,它用于在指定的毫秒数后调用函数或计算表达式。其调用格式为:

      setTimeout(code,millisec);

      其中,参数code表示要调用的函数或要执行的代码串,millisec表示在执行代码前需等待的毫秒数。

例如,setTimeout(“draw()”,1000)表示延时1秒后执行函数draw中的代码。

编写如下的HTML文件。

<!DOCTYPE html>

<html>

<head>

<title>setTimeout方法的应用</title>

</head>

<body>

<canvas id="myCanvas" width="400" height="400" style="border:3px double #996633;">

</canvas>

<script type="text/javascript">

  var canvas = document.getElementById('myCanvas');

  var ctx = canvas.getContext('2d');

  function draw(x,y,len,color)

  {

      ctx.fillStyle = color;

      ctx.fillRect(x,y,len,len);

  }

  setTimeout("draw(10,10,100,'red')",1000);

  setTimeout("draw(110,110,200,'blue')",5000);

</script>

</body>

</html>

      在浏览器中打开保存这段HTML代码的html文件,则等待1秒后,会绘制一个边长为100的红色正方形,再等待5秒,绘制一个边长为200的蓝色正方形。

      通过这个例子可以知道:(1)setTimeout()方法可以用于延时;(2)setTimeout()方法只执行code一次。如果要多次调用,则需要让code 自身再次调用 setTimeout()。

      为产生动画效果,显然得让setTimeout()方法多次执行。修改上面的HTML代码如下。

<!DOCTYPE html>

<html>

<head>

<title>setTimeout方法的应用</title>

</head>

<body>

<canvas id="myCanvas" width="400" height="400" style="border:3px double #996633;">

</canvas>

<script type="text/javascript">

  var canvas = document.getElementById('myCanvas');

  var ctx = canvas.getContext('2d');

  var i=0;

  function move()

  {

      ctx.fillStyle = 'red';

      ctx.fillRect(i,i,50,50);

      i++;

      if (i==350)

      {

          i=0;

          ctx.clearRect(0,0,400,400);

      }

      setTimeout("move()",10);

  }

  move();

</script>

</body>

</html>

      在浏览器中打开包含这段HTML代码的html文件,可以在浏览器窗口中看到一个简单的箭头伸出动画,如图1所示。

图1  简单的动画

      (2)setInterval() 方法。

      setInterval()也是HTML DOM Window对象的一个方法,它可按照指定的周期(以毫秒计)来调用函数或计算表达式。其调用格式为:

      setInterval(code,millisec);

      其中,参数code表示要调用的函数或要执行的代码串, millisec表示周期性执行或调用 code 之间的时间间隔(以毫秒计)。

      setInterval() 方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭。由 setInterval() 返回的 ID 值可用作 clearInterval() 方法的参数。

      clearInterval() 方法可取消由 setInterval() 设置的 timeout。其调用形式为:

        clearInterval(id_of_setinterval);

      其中参数id_of_setinterval必须是由 setInterval() 返回的 ID 值。

      若用setInterval() 方法实现图1所示的动画,则编写的HTML文件如下。

<!DOCTYPE html>

<html>

<head>

<title>setInterval()方法的应用</title>

</head>

<body>

<canvas id="myCanvas" width="400" height="400" style="border:3px double #996633;">

</canvas>

<script type="text/javascript">

  var canvas = document.getElementById('myCanvas');

  var ctx = canvas.getContext('2d');

  var i=0;

  function move()

  {

      ctx.fillStyle = 'red';

      ctx.fillRect(i,i,50,50);

      i++;

      if (i==350)

      {

          i=0;

          ctx.clearRect(0,0,400,400);

      }

  }

  setInterval("move()",10);

</script>

</body>

</html>

      (3)requestAnimationFrame()方法。

      requestAnimationFrame是浏览器用于定时循环操作的一个接口,类似于setTimeout,主要用途是按帧对网页进行重绘。

      编写动画循环的关键是要知道延迟时间多长合适。一方面,循环间隔必须足够短,这样才能保证不同的动画效果显得更平滑流畅;另一方面,循环间隔还要足够长,这样才能保证浏览器有能力渲染产生的变化。大多数显示器的刷新频率是60Hz,相当于每秒钟重绘60次。大多数浏览器都会对重绘操作加以限制,不超过显示器的重绘频率,因为即使超过了这个频率,用户体验也不会有提升。

      因此,最平滑动画的最佳循环间隔是1000ms/60,约等于17ms。以这个循环间隔重绘的动画是平滑的,因为这个速度最接近浏览器的最高限速。为了适应17ms的循环间隔,多重动画可能需要加以节制,以便不会完成得太快。

      虽然setTimeout()方法和setInterval()方法均可完成定时循环操作,但setTimeout()和setInterval() 都不十分精确。为它们传入的第二个参数millisec,实际上只是指定了把动画代码添加到浏览器UI线程队列以等待执行的时间。如果队列前面已经加入了其他任务,那动画代码就要等前面的任务执行完成后再执行。如果UI线程繁忙,比如忙于处理用户操作,那么即使把代码加入队列也不会立即执行。

      确定什么时候绘制下一帧是保证动画平滑的关键。然而,面对不十分精确的 setTimeout()和setInterval(),开发人员至今都没有办法确保浏览器按时绘制下一帧。因此,采用setTimeout()和setInterval(),即使优化了循环间隔,可能仍然只能接近想要的效果。

      引入requestAnimationFrame()方法的目的是为了让各种网页动画效果(DOM动画、Canvas动画、SVG动画、WebGL动画)能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。代码中使用requestAnimationFrame()方法,就是告诉浏览器希望执行一个动画,让浏览器在下一个动画帧安排一次网页重绘。

      requestAnimationFrame的优势在于充分利用显示器的刷新机制,比较节省系统资源。显示器有固定的刷新频率(60Hz或75Hz),也就是说,每秒最多只能重绘60次或75次,requestAnimationFrame的基本思想就是与这个刷新频率保持同步,利用这个刷新频率进行页面重绘。

      不过有一点需要注意,requestAnimationFrame是在主线程上完成。这意味着,如果主线程非常繁忙,requestAnimationFrame的动画效果会大打折扣。

      requestAnimationFrame使用一个回调函数作为参数。这个回调函数会在浏览器重绘之前调用。其调用格式为:

      requestID = window.requestAnimationFrame(callback);

      目前,主流浏览器(Firefox 23 / IE 10 / Chrome / Safari)都支持这个方法。可以用下面的方法,检查浏览器是否支持requestAnimationFrame。如果不支持,则自行模拟部署该方法。

        window.requestAnimFrame = (function(){

                  return  window.requestAnimationFrame        ||

                      window.webkitRequestAnimationFrame  ||

                        window.mozRequestAnimationFrame    ||

                            window.oRequestAnimationFrame      ||

                          window.msRequestAnimationFrame    ||

                          function( callback ){

                                window.setTimeout(callback, 1000 / 60);

                        };

          })();

      上面的代码按照1秒钟60次(大约每16.7毫秒一次),来模拟requestAnimationFrame。

      与 setTimeout() 和 setInterval() 方法不同,requestAnimationFrame( )不需要调用者指定帧速率,浏览器会自行决定最佳的帧效率。也就是说浏览器页面每次要重绘,就会通知requestAnimationFrame。如果浏览器绘制间隔是16.7ms,它就按这个间隔绘制;如果浏览器绘制间隔是10ms,它就按10ms绘制。这样就不会存在过度绘制的问题,动画不会丢帧。

      另外,使用requestAnimationFrame()方法,一旦页面不处于浏览器的当前标签,就会自动停止刷新。例如,页面最小化了,页面是不会进行重绘的,requestAnimationFrame自然也不会触发(因为没有通知)。页面绘制全部停止,资源高效利用,节省了CPU、GPU和电力。

      和setTimeout类似,requestAnimationFrame的回调函数只能被调用一次,并不能被重复调用(这点和setInterval不同)。因此,使用requestAnimationFrame的时候,同样需要反复调用它。

      由于setTimeout可以自定义调用时间, requestAnimationFrame的调用时间则是跟着系统的刷新频率走的,所以在实现动画的时候,setTimeout比requestAnimationFrame更加灵活, requestAnimationFrame比setTimeout表现效果更加优秀。

      若用requestAnimationFrame() 方法实现图1所示的动画,则编写的HTML文件如下。

<!DOCTYPE html>

<html>

<head>

<title>requestAnimationFrame方法的应用</title>

</head>

<body>

<canvas id="myCanvas" width="400" height="400" style="border:3px double #996633;">

</canvas>

<script type="text/javascript">

  var canvas = document.getElementById('myCanvas');

  var ctx = canvas.getContext('2d');

  var i=0;

  function move()

  {

      ctx.fillStyle = 'blue';

      ctx.fillRect(i,i,50,50);

      i++;

      if (i==350)

      {

          i=0;

          ctx.clearRect(0,0,400,400);

      }

      requestAnimationFrame(move);

  }

  move();

</script>

</body>

</html>

2.绘制简单图形实现动画

      图1的动画就是从左上角坐标位置(0,0)开始,绘制一个边长为50的红色正方形,之后每隔10毫秒后将左上角坐标位置的水平和垂直坐标均增加1,再绘制一个正方形,从而得到一个简单的箭头伸出动画效果。

      通过在画布中绘制简单图形,达到时间间隔后,擦除(有时候也可暂时不擦除)前次绘制的图形,重新绘制一个位置或大小略有变化的图形,这样就可得到动画效果。

例1  向中心交汇的箭头。

仿照图1动画思想略作变化,编写如下的HTML代码。

<!DOCTYPE html>

<html>

<head>

<title>向中心交汇的箭头</title>

<script type="text/javascript">

  var i=0;

  function draw(id)

  {

      var canvas = document.getElementById(id);

      ctx = canvas.getContext('2d');

      setInterval(painting,10);

  }

  function painting()

  {

      ctx.fillStyle = "green";

      ctx.fillRect(i,i,10,10);

      ctx.fillRect(400-i,400-i,10,10);

      ctx.fillRect(i,400-i,10,10);

      ctx.fillRect(400-i,i,10,10);

      i++;

      if (i==200)

      {

        ctx.clearRect(0,0,400,400);

        i=0;

      }

  }

</script>

</head>

<body onload="draw('myCanvas')">

<canvas id="myCanvas" width="400" height="400"  style="border:3px double #996633;">

</canvas>

</body>

</html>

      在浏览器中打开包含这段HTML代码的html文件,可以在浏览器窗口中看到如图2所示的动画。

图2  向中心交汇的箭头

      例2  逐层向里绘制的圆。

<!DOCTYPE html>

<html>

<head>

<title>层层向内画的圆</title>

<body>

<canvas id="myCanvas" width="400" height="400" style="border:3px double #996633;"></canvas>

<script type="text/javascript">

  var canvas = document.getElementById('myCanvas');

  var context = canvas.getContext('2d');

  var flag=1;

  var i=0;

  var r=180;

  function animate() {

      window.requestAnimationFrame(animate);

      draw();

  }

  function draw() {

      var dig=Math.PI/120;

      var x = Math.sin(i*dig)*r+200;

      var y = Math.cos(i*dig)*r+200;

      context.fillStyle = flag ? 'rgb(10,255,255)' : 'rgb(255,100,0)';

      context.beginPath();

      context.arc(x, y, 3, 0, Math.PI*2, true);

      context.closePath();

      context.fill();

      i++;

      if (i>240) {

        i=0;

        r=r-20;

        flag = !flag;

        if (r<=0) {

            context.clearRect(0,0,canvas.width,canvas.height);

            r=180;

        }

      }

  }

  animate();

</script>

</body>

</html>

      在浏览器中打开包含这段HTML代码的html文件,可以在浏览器窗口中看到如图3所示的动画。

龙华大道1号http://www.kinghill.cn/LongHuaDaDao1Hao/index.html

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。