KineticJS能够使用移动方法使用许多对象执行平滑动画吗?

时间:2013-10-31 19:18:09

标签: html5 canvas kineticjs

我正在尝试使用KineticJS将许多对象设置为画布动画。我在每一帧都使用内置的移动方法。众所周知,重新绘制图层是一项昂贵的操作,可能会导致性能问题,因此我只在每次执行移动操作后才调用layer.draw()。尽管如此,我制作的对象越多,性能越差,最终结果就是动画效果不佳。

为了比较KineticJS与原生画布的性能,我准备了两个同样做的演示 - 在500x500的画布上弹跳球。第一个是使用原生画布。它只是清除每个框架上的画布并绘制球。第二个使用KineticJS,一旦创建了图像对象,它就会使用move方法移动它们。

很明显,虽然本机演示使用10,100和1000个球进行相同的操作,但KineticJS演示的性能受到球数的强烈影响。 1000,它只是无法使用。可以对两个示例进行许多优化,包括使用requestAnimationFrame进行动画循环或使用KineticJS的内置Animation对象,但这些不会更改演示的性能。

所以这是两个演示。首先是原生的 - http://jsfiddle.net/uxsLN/1/

(function() {

    window.addEventListener('load', loaded, false);

    function loaded() {
        img = new Image();
        img.onload = canvasApp;
        img.src = 'ball.png';
    }

    function canvasApp() {

        var theCanvas = document.getElementById("canvas");
        var context = theCanvas.getContext("2d");

        function drawScreen() {

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

            context.strokeStyle = '#000000';
            context.strokeRect(1, 1, theCanvas.width - 2, theCanvas.height - 2);

            context.fillStyle = "#000000";

            var ball;
            for (var i = 0; i < balls.length; i++) {
                ball = balls[i];
                ball.x += ball.xunits;
                ball.y += ball.yunits;
                context.drawImage(img, ball.x, ball.y);
                if (ball.x + ball.radius * 2 > theCanvas.width || ball.x < 0) {
                    ball.angle = 180 - ball.angle;
                    updateBall(ball);
                } else if (ball.y + ball.radius * 2 > theCanvas.height || ball.y < 0) {
                    ball.angle = 360 - ball.angle;
                    updateBall(ball);
                }
            }
        }

        function updateBall(ball) {
            ball.radians = ball.angle * Math.PI / 180;
            ball.xunits = Math.cos(ball.radians) * ball.speed;
            ball.yunits = Math.sin(ball.radians) * ball.speed;
        }

        var numBalls = 1000;
        var maxSize = 8;
        var minSize = 5;
        var maxSpeed = maxSize + 5;
        var balls = [];
        var radius = 24;

        for (var i = 0; i < numBalls; i++) {

            var speed = maxSpeed - radius;
            var angle = Math.floor(Math.random() * 360);
            var radians = angle * Math.PI / 180;

            var ball = {
                x : (theCanvas.width - radius) / 2,
                y : (theCanvas.height - radius) / 2,
                radius : radius,
                speed : speed,
                angle : angle,
                xunits : Math.cos(radians) * speed,
                yunits : Math.sin(radians) * speed
            }

            balls.push(ball);
        }

        function gameLoop() {
            window.setTimeout(gameLoop, 20);
            drawScreen()
        }
        gameLoop();
    }

})();

接下来,KineticJS - http://jsfiddle.net/MNpUX/

(function() {

    window.addEventListener('load', loaded, false);

    function loaded() {
        img = new Image();
        img.onload = canvasApp;
        img.src = 'ball.png';
    }

    function canvasApp() {

        var stage = new Kinetic.Stage({
            container : 'container',
            width : 500,
            height : 500
        });

        var layer = new Kinetic.Layer();

        stage.add(layer);

        rect = new Kinetic.Rect({
            x : 0,
            y : 0,
            width : stage.getWidth(),
            height : stage.getHeight(),
            fill : '#EEEEEE',
            stroke : 'black'
        });

        layer.add(rect);

        function drawScreen() {

            var ball;
            for ( var i = 0; i < balls.length; i++) {
                ball = balls[i];
                ball.obj.move(ball.xunits, ball.yunits);
                if (ball.obj.getX() + ball.radius * 2 > stage.getWidth() || ball.obj.getX() < 0) {
                    ball.angle = 180 - ball.angle;
                    updateBall(ball);
                } else if (ball.obj.getY() + ball.radius * 2 > stage.getHeight() || ball.obj.getY() < 0) {
                    ball.angle = 360 - ball.angle;
                    updateBall(ball);
                }
            }
            layer.draw();
        }

        function updateBall(ball) {
            ball.radians = ball.angle * Math.PI / 180;
            ball.xunits = Math.cos(ball.radians) * ball.speed;
            ball.yunits = Math.sin(ball.radians) * ball.speed;
        }

        var numBalls = 1000;
        var maxSize = 8;
        var minSize = 5;
        var maxSpeed = maxSize + 5;
        var balls = [];
        var radius = 24;
        for ( var i = 0; i < numBalls; i++) {
            var speed = maxSpeed - radius;
            var angle = Math.floor(Math.random() * 360);
            var radians = angle * Math.PI / 180;
            var obj = new Kinetic.Image({
                image : img,
                x : (stage.getWidth() - radius) / 2,
                y : (stage.getHeight() - radius) / 2
            });
            layer.add(obj);
            var ball = {
                radius : radius,
                speed : speed,
                angle : angle,
                xunits : Math.cos(radians) * speed,
                yunits : Math.sin(radians) * speed,
                obj : obj
            };
            balls.push(ball);
        }

        function gameLoop() {
            window.setTimeout(gameLoop, 20);
            drawScreen()
        }
        gameLoop();
    }

})();

所以问题是 - 我是否想念KineticJS,或者它不是为了这个目的而建造的?

1 个答案:

答案 0 :(得分:2)

你可以通过以下方式获得一点速度:

  • 在舞台上开启聆听。
  • 使用layer.drawScene而不是layer.draw。 (drawScene也不会重绘点击场景。)
  • 将球数减少到500(效果看起来几乎相同)。

如果您的设计允许,请使用自定义Kinetic.Shape“更接近金属”。

Kinetic.Shape为您提供了一个包装上下文,您可以在其上运行本机上下文命令。

使用Shape,您将获得更好的结果,因为只有一个对象被管理。

这是代码和小提琴:http://jsfiddle.net/m1erickson/AVJyr/

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
    <script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v4.7.2.min.js"></script>
<style>
body{padding:20px;}
#container{
  border:solid 1px #ccc;
  margin-top: 10px;
  width:500px;
  height:500px;
}
</style>        
<script>
$(function(){

      var stage = new Kinetic.Stage({
          container: 'container',
          width: 500,
          height: 500,
          listening:false
      });
      var layer = new Kinetic.Layer();
      stage.add(layer);

      //
      var cw=stage.getWidth();
      var ch=stage.getHeight();
      var numBalls = 1000;
      var maxSize = 8;
      var minSize = 5;
      var maxSpeed = maxSize + 5;
      var balls = [];
      var radius = 24;
      // this is a custom Kinetic.Shape
      var shape;


      for (var i = 0; i < numBalls; i++) {
          var speed = maxSpeed - radius;
          var angle = Math.floor(Math.random() * 360);
          var radians = angle * Math.PI / 180;
          var ball = {
            x : (cw-radius)/2,
            y : (ch-radius)/2,
            radius : radius,
            speed : speed,
            angle : angle,
            xunits : Math.cos(radians) * speed,
            yunits : Math.sin(radians) * speed
          }
          balls.push(ball);
      }

      // load the ball image and create the Kinetic.Shape
      img = new Image();
      img.onload=function(){

          shape=new Kinetic.Shape({
              x: 0,
              y: 0,
              width:500,
              height:500,
              draggable: true,
              drawFunc: function(context) {
                  context.beginPath();
                  var ball;
                  for (var i = 0; i < balls.length; i++) {
                    ball = balls[i];
                    ball.x += ball.xunits;
                    ball.y += ball.yunits;
                    context.drawImage(img, ball.x, ball.y);
                    if (ball.x+ball.radius*2>cw || ball.x<0) {
                      ball.angle = 180 - ball.angle;
                    } else if (ball.y+ball.radius*2>ch || ball.y<0) {
                      ball.angle = 360 - ball.angle;
                    }
                    ball.radians = ball.angle * Math.PI / 180;
                    ball.xunits = Math.cos(ball.radians) * ball.speed;
                    ball.yunits = Math.sin(ball.radians) * ball.speed;
                  }
                  context.fillStrokeShape(this);
              },
          });
          layer.add(shape);

          // GO!
          gameLoop();
      }
      img.src = 'http://users-cs.au.dk/mic/dIntProg/e12/uge/4/Projekter/bouncingballs/assignment/ball.png';

      // RAF used to repeatedly redraw the custom shape
      function gameLoop(){
          window.requestAnimationFrame(gameLoop);
          layer.clear();
          shape.draw();
      }


}); // end $(function(){});

</script>       
</head>

<body>
    <div id="container"></div>
</body>
</html>