您现在的位置:首页 >> 前端 >> 内容

Canvas入门之requestAnimationFrame实现动画(代码教程)

时间:2017/12/7 11:27:36 点击:

  核心提示:尽量不要使用setInterval和setTimeout来实现动画,而应该使用requestAnimationFrame()方法来让浏览器自行决定帧速率。由于各个浏览器对requestAnimatio...

尽量不要使用setInterval和setTimeout来实现动画,而应该使用requestAnimationFrame()方法来让浏览器自行决定帧速率。

由于各个浏览器对requestAnimationFrame()的实现有所区别,首先封装一个兼容函数requestNextAnimationFrame

window.requestNextAnimationFrame =
   (function () {
      var originalWebkitRequestAnimationFrame = undefined,
          wrapper = undefined,
          callback = undefined,
          geckoVersion = 0,
          userAgent = navigator.userAgent,
          index = 0,
          self = this;

      // Workaround for Chrome 10 bug where Chrome
      // does not pass the time to the animation function

      if (window.webkitRequestAnimationFrame) {
         // Define the wrapper

         wrapper = function (time) {
           if (time === undefined) {
              time = +new Date();
           }
           self.callback(time);
         };

         // Make the switch

         originalWebkitRequestAnimationFrame = window.webkitRequestAnimationFrame;    

         window.webkitRequestAnimationFrame = function (callback, element) {
            self.callback = callback;

            // Browser calls the wrapper and wrapper calls the callback

            originalWebkitRequestAnimationFrame(wrapper, element);
         }
      }

      // Workaround for Gecko 2.0, which has a bug in
      // mozRequestAnimationFrame() that restricts animations
      // to 30-40 fps.

      if (window.mozRequestAnimationFrame) {
         // Check the Gecko version. Gecko is used by browsers
         // other than Firefox. Gecko 2.0 corresponds to
         // Firefox 4.0.

         index = userAgent.indexOf('rv:');

         if (userAgent.indexOf('Gecko') != -1) {
            geckoVersion = userAgent.substr(index + 3, 3);

            if (geckoVersion === '2.0') {
               // Forces the return statement to fall through
               // to the setTimeout() function.

               window.mozRequestAnimationFrame = undefined;
            }
         }
      }

      return window.requestAnimationFrame   ||
         window.webkitRequestAnimationFrame ||
         window.mozRequestAnimationFrame    ||
         window.oRequestAnimationFrame      ||
         window.msRequestAnimationFrame     ||

         function (callback, element) {
            var start,
                finish;

            window.setTimeout( function () {
               start = +new Date();
               callback(start);
               finish = +new Date();

               self.timeout = 1000 / 60 - (finish - start);

            }, self.timeout);
         };
      }
   )
();

示例: 使用requestAnimationFrame()方法来绘制动画

html

<head>
     <title>Using requestAnimationFrame()</title>

      <style> 
         body {
            background: #dddddd;
         }

         #canvas {
            background: #ffffff;
            cursor: pointer;
            margin-left: 10px;
            margin-top: 10px;
            -webkit-box-shadow: 3px 3px 6px rgba(0,0,0,0.5);
            -moz-box-shadow: 3px 3px 6px rgba(0,0,0,0.5);
            box-shadow: 3px 3px 6px rgba(0,0,0,0.5);
         }

         #controls {
            margin-top: 10px;
            margin-left: 15px;
         }
      </style>
   </head>

   <body>
      <p id='controls'>
         <input id='animateButton' type='button' value='Animate'/>
      </p>

      <canvas id='canvas' width='750' height='500'>
         Canvas not supported
      </canvas>

      <script src='../../shared/js/requestNextAnimationFrame.js'></script>
      <script src='example.js'></script>
   </body>

js

var canvas = document.getElementById('canvas'),
    context = canvas.getContext('2d'),
    paused = true,
    //定义三个球
    discs = [
      { 
         x: 150,
         y: 250,
         lastX: 150,
         lastY: 250,
         velocityX: -3.2,
         velocityY: 3.5,
         radius: 25,
         innerColor: 'rgba(255,255,0,1)',
         middleColor: 'rgba(255,255,0,0.7)',
         outerColor: 'rgba(255,255,0,0.5)',
         strokeStyle: 'gray',
      },

      { 
         x: 50,
         y: 150,
         lastX: 50,
         lastY: 150,
         velocityX: 2.2,
         velocityY: 2.5,
         radius: 25,
         innerColor: 'rgba(100,145,230,1.0)',
         middleColor: 'rgba(100,145,230,0.7)',
         outerColor: 'rgba(100,145,230,0.5)',
         strokeStyle: 'blue'
      },

      { 
         x: 150,
         y: 75,
         lastX: 150,
         lastY: 75,
         velocityX: 1.2,
         velocityY: 1.5,
         radius: 25,
         innerColor: 'rgba(255,0,0,1.0)',
         middleColor: 'rgba(255,0,0,0.7)',
         outerColor: 'rgba(255,0,0,0.5)',
         strokeStyle: 'orange'
      },
   ],
   numDiscs = discs.length,
   animateButton = document.getElementById('animateButton');

// Functions.....................................................

function drawBackground() {
   var STEP_Y = 12,
       i = context.canvas.height;

   context.strokeStyle = 'lightgray';
   context.lineWidth = 0.5;

   while(i > STEP_Y*4) {
      context.beginPath();
      context.moveTo(0, i);
      context.lineTo(context.canvas.width, i);
      context.stroke();
      i -= STEP_Y;
   }

   context.save();

   context.strokeStyle = 'rgba(100,0,0,0.3)';
   context.lineWidth = 1;

   context.beginPath();

   context.moveTo(35,0);
   context.lineTo(35,context.canvas.height);
   context.stroke();

   context.restore();
}

function update() {
   var disc = null;

   for(var i=0; i < numDiscs; ++i) {
      disc = discs[i];
      //边缘检测
      if (disc.x + disc.velocityX + disc.radius > context.canvas.width ||
          disc.x + disc.velocityX - disc.radius < 0) 
         disc.velocityX = -disc.velocityX;

      if (disc.y + disc.velocityY + disc.radius > context.canvas.height ||
          disc.y + disc.velocityY - disc.radius  < 0) 
         disc.velocityY= -disc.velocityY;

      disc.x += disc.velocityX;
      disc.y += disc.velocityY;
   }
}

function draw() {
   var disc = discs[i];

   for(var i=0; i < numDiscs; ++i) {
      disc = discs[i];

      gradient = context.createRadialGradient(disc.x, disc.y, 0,
                         disc.x, disc.y, disc.radius);

      gradient.addColorStop(0.3, disc.innerColor);
      gradient.addColorStop(0.5, disc.middleColor);
      gradient.addColorStop(1.0, disc.outerColor);

      context.save();
      context.beginPath();
      context.arc(disc.x, disc.y, disc.radius, 0, Math.PI*2, false);
      context.fillStyle = gradient;
      context.strokeStyle = disc.strokeStyle;
      context.fill();
      context.stroke();
      context.restore();
   }
}

// 动画.....................................................
function animate(time) {
   if (!paused) {
      context.clearRect(0,0,canvas.width,canvas.height);
      drawBackground();
      update();
      draw();

      window.requestNextAnimationFrame(animate);
   }
}

// 初始化................................................

context.font = '48px Helvetica';

animateButton.onclick = function (e) {
   paused = paused ? false : true;
   if (paused) {
      animateButton.value = 'Animate';
   }
   else {
     window.requestNextAnimationFrame(animate);
      animateButton.value = 'Pause';
   }
};

Canvas入门之requestAnimationFrame实现动画(代码教程)

帧速率计算

动画图形的显示频率叫做“帧速率”frame rate。

通常来说,有必要计算一下帧速率。计算方式:

var lastTime = 0;
function calculateFps(){
    var now=(+new Date),fps = 1000/(now-lastTime);
    lastTime = now;
    return fps;
}
function animate(time){
    eraseBackground();
    drawBackground();
    update();
    draw();
    context.fillStyle='cornflowerblue';
    context.fillText(calculateFps().toFixed()+' fps',20,60);
    window.requestNextAnimationFrame(animate);
}
window.requestNextAnimationFrame(animate);

以不同的帧速率来执行各种任务

一些不重要的任务,可以以低速率来执行。下面示例中循环根据上一次更新fps数值的时间来判断当前是否已经过了一秒钟,如果是的话,就再次更新fps数值:

var lastFpsUpdateTime =0, lastFpsUpdate =0;
function animate(time){
    var fps =0;
    if(time==undefined){
        time = +new Date;
    }
    if(!paused){
        eraseBackground();
        drawBackground();
        update(time);
        draw();
        fps=calculateFps();
        // 每秒更新frame rate值
        if(now - lastFpsUpdateTime>1000){
            lastFpsUpdateTime = now;
            lastFpsUpdate = fps;
        }
        context.fillStyle = 'cornflowerblue';
        context.fillText(lastFpsUpdate.toFixed() + ' fps',50,48);
    }
}

Tags:CA AN NV VA 
作者:网络 来源:谢厂节的博客