画布流畅动画

时间:2016-03-30 18:03:49

标签: javascript canvas html5-canvas

**您需要以全屏模式运行代码段。

看一下下面的例子。如果你点击左右,你会注意到高速行驶时球几乎变得模糊。

有什么方法可以解决这个问题吗?这是由于60fps?如果有,有办法增加它吗?

当球以更高的速度间隔开时,似乎会发生这种情况。您可以在下面的图片中看到这一点。

enter image description here

'use strict';

// Todo
// - Make the ball spin
// - Make the ball squish
// - Add speed lines


(function () {

  const canvas = document.getElementsByClassName('canvas')[0],
        c = canvas.getContext('2d');


  // -----------------------------------
  // Resize the canvas to be full screen
  // -----------------------------------

  window.addEventListener('resize', resizeCanvas, false);

  function resizeCanvas() {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;

    // ---------
    // Variables
    // ---------

    var circleRadius = 40,
        x = (canvas.width/2) - circleRadius, // inital x position of the ball
        y = (canvas.height/2) - circleRadius, // inital y position of the ball
        vx = 0, // velocity
        vy = 0, // velocity
        gravity = 0.8,
        dampening = 0.5,
        pullStrength = 0.04,
        segments = 4,
        bezieCircleFormula = (4/3)*Math.tan(Math.PI/(2*segments)), // http://stackoverflow.com/a/27863181/2040509
        pointOffset = {
          positive: bezieCircleFormula*circleRadius,
          negative: circleRadius-(bezieCircleFormula*circleRadius)
        },
        // Each side has 3 points, bezier 1, circle point, bezier 2
        // These are listed below in clockwise order.
        // So top has: left bezier, circle point, right bezier
        // Right has: top bezier, circle point, bottom bezier
        circlePoints = {
          top: [
            [x+pointOffset.negative, y],
            [x+circleRadius, y],
            [x+pointOffset.positive+circleRadius, y]
          ],
          right: [
            [x+circleRadius*2, y+pointOffset.negative],
            [x+circleRadius*2, y+circleRadius],
            [x+circleRadius*2, y+pointOffset.positive+circleRadius]
          ],
          bottom: [
            [x+pointOffset.positive+circleRadius, y+circleRadius*2],
            [x+circleRadius, y+circleRadius*2],
            [x+pointOffset.negative, y+circleRadius*2]
          ],
          left: [
            [x, y+pointOffset.positive+circleRadius],
            [x, y+circleRadius],
            [x, y+pointOffset.negative]
          ]
        };



    // --------------------
    // Ball squish function
    // --------------------
    // For `side` you can pass `top`, `right`, `bottom`, `left`
    // For `amount` use an interger

    function squish (side, squishAmount) {
      for (let i = 0; i < circlePoints[side].length; i++) {
        if (side === 'top') {
          circlePoints[side][i][1] += squishAmount;
        } else if (side === 'right') {
          circlePoints[side][i][0] -= squishAmount;
        } else if (side === 'bottom') {
          circlePoints[side][i][1] -= squishAmount;
        } else if (side === 'left') {
          circlePoints[side][i][0] += squishAmount;
        }
      }
    }



    // ------------------
    // Animation Function
    // ------------------

    function render () {

      // Clear the canvas
      c.clearRect(0, 0, canvas.width, canvas.height);



      // -----------------
      // Draw the elements
      // -----------------

      // Ground
      let groundHeight = 200;

      c.beginPath();
      c.fillStyle = '#9cccc8';
      c.fillRect(0, canvas.height - groundHeight, canvas.width, groundHeight);

      // Bezier circle
      c.beginPath();
      c.fillStyle = '#cf2264';
      c.moveTo(circlePoints.left[1][0], circlePoints.left[1][1]);
      c.bezierCurveTo(circlePoints.left[2][0], circlePoints.left[2][1], circlePoints.top[0][0], circlePoints.top[0][1], circlePoints.top[1][0], circlePoints.top[1][1]);
      c.bezierCurveTo(circlePoints.top[2][0], circlePoints.top[2][1], circlePoints.right[0][0], circlePoints.right[0][1], circlePoints.right[1][0], circlePoints.right[1][1]);
      c.bezierCurveTo(circlePoints.right[2][0], circlePoints.right[2][1], circlePoints.bottom[0][0], circlePoints.bottom[0][1], circlePoints.bottom[1][0], circlePoints.bottom[1][1]);
      c.bezierCurveTo(circlePoints.bottom[2][0], circlePoints.bottom[2][1], circlePoints.left[0][0], circlePoints.left[0][1], circlePoints.left[1][0], circlePoints.left[1][1]);
      c.fill();
      c.closePath();



      // -------------------------------
      // Recalculate circle co-ordinates
      // -------------------------------

      circlePoints = {
        top: [
          [x+pointOffset.negative, y],
          [x+circleRadius, y],
          [x+pointOffset.positive+circleRadius, y]
        ],
        right: [
          [x+circleRadius*2, y+pointOffset.negative],
          [x+circleRadius*2, y+circleRadius],
          [x+circleRadius*2, y+pointOffset.positive+circleRadius]
        ],
        bottom: [
          [x+pointOffset.positive+circleRadius, y+circleRadius*2],
          [x+circleRadius, y+circleRadius*2],
          [x+pointOffset.negative, y+circleRadius*2]
        ],
        left: [
          [x, y+pointOffset.positive+circleRadius],
          [x, y+circleRadius],
          [x, y+pointOffset.negative]
        ]
      };



      // -----------------
      // Animation Gravity
      // -----------------


      // Increment gravity
      vy += gravity;

      // Increment velocity
      y += vy;
      x += vx;



      // ----------
      // Boundaries
      // ----------

      // Bottom boundary
      if ((y + (circleRadius * 2)) > canvas.height - groundHeight/2) {
        y = canvas.height - groundHeight/2 - (circleRadius * 2);
        vy *= -1;

        // Dampening
        vy *= dampening;
        vx *= dampening;

        console.log(vy);

        if (vy > -2.4) {
          dampening = 0;
        } else {
          // squish('top', 20);
        }
      }

      // Right boundary
      if ((x + (circleRadius * 2)) > canvas.width) {
        x = canvas.width - (circleRadius * 2);
        vx *= -1;

        // Dampening
        vy *= dampening;
        vx *= dampening;
      }

      // Left boundary
      if ((x + (circleRadius * 2)) < 0 + (circleRadius * 2)) {
        x = 0;
        vx *= -1;

        // Dampening
        vy *= dampening;
        vx *= dampening;
      }

      // Top boundary
      if (y < 0) {
        y = 0;
        vy *= -1;

        // Dampening
        vy *= dampening;
        vx *= dampening;
      }

      requestAnimationFrame(render);
    }



    // -----------
    // Click event
    // -----------

    canvas.addEventListener('mousedown', function (e) {
      let dx = e.pageX - x,
          dy = e.pageY - y;

      if (dampening === 0) {
        dampening = 0.5;
      }

      vx += dx * pullStrength;
      vy += dy * pullStrength;
    });

    render();

  }
  resizeCanvas();

})();
body {
  margin: 0;
}

canvas {
  background: #ddf6f5;
  display: block;
}
<canvas class="canvas"></canvas>

1 个答案:

答案 0 :(得分:2)

目前使用requestAnimationFrame时,您将从浏览器获得60fps。您可以获得更快的帧速率,因为很难保持同步甚至知道显示器运行的帧速率。与一些运行120 + fps的本机应用程序和机器相比,60fps速度较慢,但​​它是目前浏览器中最好的。

我刚刚为你的代码添加了一些代码(对不起,我的代码有点乱),只显示了renderTime&#34;绿线&#34; (代码花费每帧渲染场景的时间)和帧速率&#34;红线&#34;再加上averages.I不包括渲染我添加的行和文本的时间,所以它们不会影响渲染时间,但会影响帧速率。

你不会在渲染时间低于帧时间的情况下对图形施加沉重的负担,但是你可能会注意到红线上偶尔会出现尖峰。当浏览器丢弃某些帧并导致动画看起来不平滑时会发生这种情况。

你只为你的javascript(不包括工人)获得一个线程,所以对于大多数机器来说这不到cpu功率的1/8,而且与处理CSS动画的本机代码相比,javascript是一种慢速语言。 (我不确定,但CSS动画也可能从javascript无法访问的线程中获得一些好处)

如果偶然的话,我添加的红线遍布整个地方(根本没有平坦的直线部分)且平均帧速率不接近60fps,您的浏览器可能会禁用显示同步,并且只是在您显示场景时立即显示完成了。

我添加了此代码,以直观地展示性能和帧速率。对于实际测试,您不应该有显示部分,只测量时间仅在测试期后显示结果。

&#13;
&#13;
'use strict';

// Todo
// - Make the ball spin
// - Make the ball squish
// - Add speed lines


(function () {

  const canvas = document.getElementsByClassName('canvas')[0],
        c = canvas.getContext('2d');


  // -----------------------------------
  // Resize the canvas to be full screen
  // -----------------------------------

  window.addEventListener('resize', resizeCanvas, false);

  function resizeCanvas() {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;

    // ---------
    // Variables
    // ---------

    var circleRadius = 40,
        x = (canvas.width/2) - circleRadius, // inital x position of the ball
        y = (canvas.height/2) - circleRadius, // inital y position of the ball
        vx = 0, // velocity
        vy = 0, // velocity
        gravity = 0.8,
        dampening = 0.5,
        pullStrength = 0.04,
        segments = 4,
        bezieCircleFormula = (4/3)*Math.tan(Math.PI/(2*segments)), // http://stackoverflow.com/a/27863181/2040509
        pointOffset = {
          positive: bezieCircleFormula*circleRadius,
          negative: circleRadius-(bezieCircleFormula*circleRadius)
        },
        // Each side has 3 points, bezier 1, circle point, bezier 2
        // These are listed below in clockwise order.
        // So top has: left bezier, circle point, right bezier
        // Right has: top bezier, circle point, bottom bezier
        circlePoints = {
          top: [
            [x+pointOffset.negative, y],
            [x+circleRadius, y],
            [x+pointOffset.positive+circleRadius, y]
          ],
          right: [
            [x+circleRadius*2, y+pointOffset.negative],
            [x+circleRadius*2, y+circleRadius],
            [x+circleRadius*2, y+pointOffset.positive+circleRadius]
          ],
          bottom: [
            [x+pointOffset.positive+circleRadius, y+circleRadius*2],
            [x+circleRadius, y+circleRadius*2],
            [x+pointOffset.negative, y+circleRadius*2]
          ],
          left: [
            [x, y+pointOffset.positive+circleRadius],
            [x, y+circleRadius],
            [x, y+pointOffset.negative]
          ]
        };



    // --------------------
    // Ball squish function
    // --------------------
    // For `side` you can pass `top`, `right`, `bottom`, `left`
    // For `amount` use an interger

    function squish (side, squishAmount) {
      for (let i = 0; i < circlePoints[side].length; i++) {
        if (side === 'top') {
          circlePoints[side][i][1] += squishAmount;
        } else if (side === 'right') {
          circlePoints[side][i][0] -= squishAmount;
        } else if (side === 'bottom') {
          circlePoints[side][i][1] -= squishAmount;
        } else if (side === 'left') {
          circlePoints[side][i][0] += squishAmount;
        }
      }
    }



    // ------------------
    // Animation Function
    // ------------------ 
    var lastTime = new Date().valueOf();
    var frameTimes = [];
    var renderTimes = []
    var frameTimeWritePos = 0;
    var frameTimeReadStartPos = 0;
    var frameTimeMaxSample = Math.floor(canvas.width/8);
    var maxTime = 0;
console.log(maxTime);

    function recordFrameTime(time,renderTime){
        frameTimes[frameTimeWritePos % frameTimeMaxSample] = time-lastTime;
        renderTimes[frameTimeWritePos % frameTimeMaxSample] = renderTime;
        maxTime = Math.min(1000/20,Math.max(maxTime, renderTime, time-lastTime));
        lastTime = time;
        frameTimeWritePos = (frameTimeWritePos + 1) % frameTimeMaxSample;
        if(frameTimeWritePos === frameTimeReadStartPos){
            frameTimeReadStartPos = (frameTimeReadStartPos + 1) % frameTimeMaxSample;
        }
    }
    function drawFrameTimes(){
        var yScale,xScale;
        var t1 = 0;
        var t2 = 0;
        var c1 = 0;
        var h = canvas.height;
        yScale = h / maxTime;
        xScale = canvas.width / frameTimeMaxSample;
        c.lineWidth = 2;
        c.strokeStyle = "red";
        var i = frameTimeReadStartPos;
        var sx = i;
        c.beginPath();
        c.moveTo((i-sx)*xScale ,h - frameTimes[(i %frameTimeMaxSample)]*yScale);
        while ( (i %frameTimeMaxSample) !== frameTimeWritePos){                 
            c.lineTo((i-sx)*xScale  ,h - frameTimes[(i %frameTimeMaxSample)]*yScale);
            t1 += frameTimes[(i %frameTimeMaxSample)];
            c1 += 1;
            i += 1;

        }
        t1 /= c1;
        t1 = (1000/t1).toFixed(2);
        c1 = 0;
        c.stroke();
        i = frameTimeReadStartPos;
  
        c.strokeStyle = "Green";
        c.beginPath();
        c.moveTo((i-sx)*xScale ,h - renderTimes[(i %frameTimeMaxSample)]*yScale);
        while ( (i %frameTimeMaxSample) !== frameTimeWritePos){                 
            c.lineTo((i-sx)*xScale ,h - renderTimes[(i %frameTimeMaxSample)]*yScale);
            i += 1;
            t2 += renderTimes[(i %frameTimeMaxSample)];
            c1 += 1;
        }
        t2/= c1;
        c.stroke();
        //c.beginPath();
        //c.strokeStyle = "white";
        //c.moveTo(0,h-(1000/60)*yScale);
       // c.lineTo(canvas.width,h-(1000/60)*yScale);
       // c.stroke();
        c.font="36px arial";
        c.fillStyle = "black";
        c.fillText("R:" + t2.toFixed(2)+ "ms "+t1+"fps", 20,40);
    }
    var bRenders = 0;
    function render (time) {
      if(isNaN(time)){
         time = performance.now();
         bRenders += 1;
      } 
      var startTime = performance.now();

      // Clear the canvas
      c.clearRect(0, 0, canvas.width, canvas.height);



      // -----------------
      // Draw the elements
      // -----------------

      // Ground
      let groundHeight = 200;

      c.beginPath();
      c.fillStyle = '#9cccc8';
      c.fillRect(0, canvas.height - groundHeight, canvas.width, groundHeight);

      // Bezier circle
      c.beginPath();
      c.fillStyle = '#cf2264';
      c.moveTo(circlePoints.left[1][0], circlePoints.left[1][1]);
      c.bezierCurveTo(circlePoints.left[2][0], circlePoints.left[2][1], circlePoints.top[0][0], circlePoints.top[0][1], circlePoints.top[1][0], circlePoints.top[1][1]);
      c.bezierCurveTo(circlePoints.top[2][0], circlePoints.top[2][1], circlePoints.right[0][0], circlePoints.right[0][1], circlePoints.right[1][0], circlePoints.right[1][1]);
      c.bezierCurveTo(circlePoints.right[2][0], circlePoints.right[2][1], circlePoints.bottom[0][0], circlePoints.bottom[0][1], circlePoints.bottom[1][0], circlePoints.bottom[1][1]);
      c.bezierCurveTo(circlePoints.bottom[2][0], circlePoints.bottom[2][1], circlePoints.left[0][0], circlePoints.left[0][1], circlePoints.left[1][0], circlePoints.left[1][1]);
      c.fill();
      c.closePath();



      // -------------------------------
      // Recalculate circle co-ordinates
      // -------------------------------

      circlePoints = {
        top: [
          [x+pointOffset.negative, y],
          [x+circleRadius, y],
          [x+pointOffset.positive+circleRadius, y]
        ],
        right: [
          [x+circleRadius*2, y+pointOffset.negative],
          [x+circleRadius*2, y+circleRadius],
          [x+circleRadius*2, y+pointOffset.positive+circleRadius]
        ],
        bottom: [
          [x+pointOffset.positive+circleRadius, y+circleRadius*2],
          [x+circleRadius, y+circleRadius*2],
          [x+pointOffset.negative, y+circleRadius*2]
        ],
        left: [
          [x, y+pointOffset.positive+circleRadius],
          [x, y+circleRadius],
          [x, y+pointOffset.negative]
        ]
      };



      // -----------------
      // Animation Gravity
      // -----------------


      // Increment gravity
      vy += gravity;

      // Increment velocity
      y += vy;
      x += vx;



      // ----------
      // Boundaries
      // ----------

      // Bottom boundary
      if ((y + (circleRadius * 2)) > canvas.height - groundHeight/2) {
        y = canvas.height - groundHeight/2 - (circleRadius * 2);
        vy *= -1;

        // Dampening
        vy *= dampening;
        vx *= dampening;

       // console.log(vy);

        if (vy > -2.4) {
          dampening = 0;
        } else {
          // squish('top', 20);
        }
      }

      // Right boundary
      if ((x + (circleRadius * 2)) > canvas.width) {
        x = canvas.width - (circleRadius * 2);
        vx *= -1;

        // Dampening
        vy *= dampening;
        vx *= dampening;
      }

      // Left boundary
      if ((x + (circleRadius * 2)) < 0 + (circleRadius * 2)) {
        x = 0;
        vx *= -1;

        // Dampening
        vy *= dampening;
        vx *= dampening;
      }

      // Top boundary
      if (y < 0) {
        y = 0;
        vy *= -1;

        // Dampening
        vy *= dampening;
        vx *= dampening;
      }

      requestAnimationFrame(render);
      recordFrameTime(time,(performance.now()-startTime));
      drawFrameTimes();

    }



    // -----------
    // Click event
    // -----------

    canvas.addEventListener('mousedown', function (e) {
      let dx = e.pageX - x,
          dy = e.pageY - y;

      if (dampening === 0) {
        dampening = 0.5;
      }

      vx += dx * pullStrength;
      vy += dy * pullStrength;
    });

    render();

  }
  resizeCanvas();

})();
&#13;
body {
  margin: 0;
}

canvas {
  background: #ddf6f5;
  display: block;
}
&#13;
<canvas class="canvas"></canvas>
&#13;
&#13;
&#13;