Littlee
Littlee

Reputation: 4327

Fabric.js: using animate method on many object is so slow

I'm using animate method to animate 100 objects, but in my case, the performance is so slow, how do I fix it?

my demo code: https://jsfiddle.net/cs6jqj2w/

Upvotes: 2

Views: 1991

Answers (2)

David Basalla
David Basalla

Reputation: 3106

Sounds like you've got it working now. Just thought I would add that the docs on http://fabricjs.com/fabric-intro-part-2 also mention that you might want to use requestAnimationFrame instead of the onChange callback when animating lots of objects:

The reason animate doesn't automatically re-render canvas after each change is due to performance. After all, we can have hundreds or thousands animating objects on canvas, and it wouldn't be good if every one of them tried to re-render screen. In the case of many objects, you could use something like requestAnimationFrame (or other timer-based) loop to render canvas continuosly on its own, without calling renderAll for each object. But most of the time, you'll probably need to explicitly specify canvas.renderAll as "onChange" callback.

I had similar problems to yours, and this advice worked quite well.

Upvotes: 0

Observer
Observer

Reputation: 3706

Please take a look at fabricJS demo

Also, I modified little bit that demo using your function for generating random numbers and created 100 shpaes in this fiddle

(function() {
  var canvas = this.__canvas = new fabric.Canvas('c');
  fabric.Object.prototype.transparentCorners = false;

  var Cross = fabric.util.createClass(fabric.Object, {
    objectCaching: false,
    initialize: function(options) {
      this.callSuper('initialize', options);
      this.animDirection = 'up';

      this.width = 100;
      this.height = 100;

      this.w1 = this.h2 = 100;
      this.h1 = this.w2 = 30;
    },

    animateWidthHeight: function() {
      var interval = 2;

      if (this.h2 >= 30 && this.h2 <= 100) {
        var actualInterval = (this.animDirection === 'up' ? interval : -interval);
        this.h2 += actualInterval;
        this.w1 += actualInterval;
      }

      if (this.h2 >= 100) {
        this.animDirection = 'down';
        this.h2 -= interval;
        this.w1 -= interval;
      }
      if (this.h2 <= 30) {
        this.animDirection = 'up';
        this.h2 += interval;
        this.w1 += interval;
      }
    },

    _render: function(ctx) {
      ctx.fillRect(-this.w1 / 2, -this.h1 / 2, this.w1, this.h1);
      ctx.fillRect(-this.w2 / 2, -this.h2 / 2, this.w2, this.h2);
    }
  });


  for (var i = 0; i < 100; i++){
   canvas.add(
   new Cross({ top: getRandomInt(0,500), left: getRandomInt(0,500)})
   );
  }


  setTimeout(function animate() {
    canvas.forEachObject(function(obj){ obj.animateWidthHeight(); obj.dirty = true; });
    canvas.renderAll();
    setTimeout(animate, 10);
  }, 10);
})();

function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

UPDATE: Your animation didn't work because you had to much rendering of the canvas.

You have to generate 99 items without rendering and last one with the rendering. Also, last item has to me with the maximum duration for the animation in order to complete animation for all shapes.

var fabric = window.fabric

var canvas = new fabric.StaticCanvas('c')


function createItem(canvas) {
  var item = new fabric.Circle({
    left: -100,
    top: getRandomInt(0, 500),
    opacity: Math.random().toFixed(2),
    radius: getRandomInt(10, 50),
  })
  item.keepGoing = true
  canvas.add(item)
 // itemTopAnim(canvas, item, getNextTop(item.top))
//  itemLeftAnim(canvas, item)
return item;
}

function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

function getNextTop(top) {
  if (top < (canvas.height / 2)) {
    return top + getRandomInt(50, 200)
  }
  return top - getRandomInt(50, 200)
}

function itemTopAnim(canvas, item, top) {
  item.animate('top', top, {
    duration: getRandomInt(1, 3) * 1000,
   // onChange: canvas.renderAll.bind(canvas),
    easing: fabric.util.ease.easeInOutCubic,
    onComplete: function() {
      item.keepGoing && itemTopAnim(canvas, item, getNextTop(item.top))
    }
  })
}

function itemTopAnimLast(canvas, item, top) {
  item.animate('top', top, {
    duration: 3 * 1000,
    onChange: canvas.renderAll.bind(canvas),
    easing: fabric.util.ease.easeInOutCubic,
    onComplete: function() {
      item.keepGoing && itemTopAnim(canvas, item, getNextTop(item.top))
    }
  })
}

function itemLeftAnim(canvas, item) {
  item.animate('left', canvas.width - item.radius, {
    duration: getRandomInt(5, 10) * 1000,
    //onChange: canvas.renderAll.bind(canvas),
    onComplete: function() {
      item.keepGoing = false
    }
  })
}

function itemLeftAnimLast(canvas, item) {
  item.animate('left', canvas.width - item.radius, {
    duration: 10 * 1000,
    onChange: canvas.renderAll.bind(canvas),
    onComplete: function() {
      item.keepGoing = true
    }
  })
}

for (var i = 0; i < 100; i++) {
var item = createItem(canvas);
if (i == 99){
itemLeftAnimLast(canvas, item)
itemTopAnimLast(canvas, item, getNextTop(item.top))
} else {
itemLeftAnim(canvas, item)
itemTopAnim(canvas, item, getNextTop(item.top))
}

}

Check this updated fiddle

Hopefully, it gives you more idea how it works right now.

Upvotes: 2

Related Questions