1

我正在尝试使用 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 的一些东西,还是它不是为了这样的目的而构建的?

4

1 回答 1

2

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

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

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

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

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

这是代码和小提琴: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>
于 2013-11-01T00:32:04.140 回答