konva/src/Animation.js

316 lines
9.7 KiB
JavaScript
Raw Normal View History

2015-05-04 17:02:16 +08:00
(function(Konva) {
'use strict';
var BATCH_DRAW_STOP_TIME_DIFF = 500;
2015-05-04 17:02:16 +08:00
var now = (function() {
2015-01-27 15:07:51 +08:00
if (Konva.root.performance && Konva.root.performance.now) {
2014-02-27 08:49:18 +08:00
return function() {
2015-01-27 15:07:51 +08:00
return Konva.root.performance.now();
2014-02-27 08:49:18 +08:00
};
}
2015-10-22 13:32:07 +08:00
return function() {
return new Date().getTime();
};
})();
2015-05-04 17:02:16 +08:00
function FRAF(callback) {
setTimeout(callback, 1000 / 60);
}
var RAF = (function(){
2015-01-27 15:07:51 +08:00
return Konva.root.requestAnimationFrame
|| Konva.root.webkitRequestAnimationFrame
|| Konva.root.mozRequestAnimationFrame
|| Konva.root.oRequestAnimationFrame
|| Konva.root.msRequestAnimationFrame
|| FRAF;
})();
2015-05-04 17:02:16 +08:00
function requestAnimFrame() {
2015-01-27 15:07:51 +08:00
return RAF.apply(Konva.root, arguments);
}
2015-05-04 17:02:16 +08:00
/**
* Animation constructor. A stage is used to contain multiple layers and handle
* @constructor
2015-01-27 15:07:51 +08:00
* @memberof Konva
2013-05-19 13:30:57 +08:00
* @param {Function} func function executed on each animation frame. The function is passed a frame object, which contains
* timeDiff, lastTime, time, and frameRate properties. The timeDiff property is the number of milliseconds that have passed
* since the last animation frame. The lastTime property is time in milliseconds that elapsed from the moment the animation started
* to the last animation frame. The time property is the time in milliseconds that ellapsed from the moment the animation started
2014-05-01 09:24:51 +08:00
* to the current animation frame. The frameRate property is the current frame rate in frames / second. Return false from function,
* if you don't need to redraw layer/layers on some frames.
2015-01-27 15:07:51 +08:00
* @param {Konva.Layer|Array} [layers] layer(s) to be redrawn on each animation frame. Can be a layer, an array of layers, or null.
2013-05-19 13:30:57 +08:00
* Not specifying a node will result in no redraw.
* @example
2014-04-04 11:17:09 +08:00
* // move a node to the right at 50 pixels / second
* var velocity = 50;
2013-05-19 13:30:57 +08:00
*
2015-01-27 15:07:51 +08:00
* var anim = new Konva.Animation(function(frame) {
2014-04-04 11:17:09 +08:00
* var dist = velocity * (frame.timeDiff / 1000);
* node.move(dist, 0);
* }, layer);
2013-05-19 13:30:57 +08:00
*
* anim.start();
*/
2015-01-27 15:07:51 +08:00
Konva.Animation = function(func, layers) {
var Anim = Konva.Animation;
this.func = func;
this.setLayers(layers);
this.id = Anim.animIdCounter++;
this.frame = {
time: 0,
timeDiff: 0,
lastTime: now()
};
};
/*
* Animation methods
*/
2015-01-27 15:07:51 +08:00
Konva.Animation.prototype = {
/**
* set layers to be redrawn on each animation frame
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Animation.prototype
* @param {Konva.Layer|Array} [layers] layer(s) to be redrawn.  Can be a layer, an array of layers, or null. Not specifying a node will result in no redraw.
2015-10-22 13:32:07 +08:00
* @return {Konva.Animation} this
*/
setLayers: function(layers) {
var lays = [];
// if passing in no layers
if (!layers) {
2013-05-12 11:07:20 +08:00
lays = [];
}
// if passing in an array of Layers
2015-01-27 15:07:51 +08:00
// NOTE: layers could be an array or Konva.Collection. for simplicity, I'm just inspecting
// the length property to check for both cases
else if (layers.length > 0) {
lays = layers;
}
// if passing in a Layer
else {
lays = [layers];
}
this.layers = lays;
2015-10-22 13:32:07 +08:00
return this;
},
/**
* get layers
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Animation.prototype
2015-10-22 13:32:07 +08:00
* @return {Array} Array of Konva.Layer
*/
getLayers: function() {
return this.layers;
},
/**
* add layer. Returns true if the layer was added, and false if it was not
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Animation.prototype
2015-10-22 13:32:07 +08:00
* @param {Konva.Layer} layer to add
* @return {Bool} true if layer is added to animation, otherwise false
*/
addLayer: function(layer) {
var layers = this.layers,
2015-05-04 17:02:16 +08:00
len = layers.length, n;
2015-05-04 17:02:16 +08:00
// don't add the layer if it already exists
for (n = 0; n < len; n++) {
if (layers[n]._id === layer._id){
return false;
}
}
this.layers.push(layer);
return true;
},
/**
2013-01-14 14:52:31 +08:00
* determine if animation is running or not. returns true or false
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Animation.prototype
2015-10-22 13:32:07 +08:00
* @return {Bool} is animation running?
*/
isRunning: function() {
2015-01-27 15:07:51 +08:00
var a = Konva.Animation,
animations = a.animations,
len = animations.length,
n;
for(n = 0; n < len; n++) {
if(animations[n].id === this.id) {
return true;
}
}
return false;
},
/**
* start animation
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Animation.prototype
2015-10-22 13:32:07 +08:00
* @return {Konva.Animation} this
*/
start: function() {
2015-01-27 15:07:51 +08:00
var Anim = Konva.Animation;
this.stop();
this.frame.timeDiff = 0;
this.frame.lastTime = now();
Anim._addAnimation(this);
2015-10-22 13:32:07 +08:00
return this;
},
/**
* stop animation
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Animation.prototype
2015-10-22 13:32:07 +08:00
* @return {Konva.Animation} this
*/
stop: function() {
2015-01-27 15:07:51 +08:00
Konva.Animation._removeAnimation(this);
2015-10-22 13:32:07 +08:00
return this;
},
2013-01-08 11:36:12 +08:00
_updateFrameObject: function(time) {
this.frame.timeDiff = time - this.frame.lastTime;
this.frame.lastTime = time;
this.frame.time += this.frame.timeDiff;
this.frame.frameRate = 1000 / this.frame.timeDiff;
}
};
2015-01-27 15:07:51 +08:00
Konva.Animation.animations = [];
Konva.Animation.animIdCounter = 0;
Konva.Animation.animRunning = false;
2015-01-27 15:07:51 +08:00
Konva.Animation._addAnimation = function(anim) {
this.animations.push(anim);
this._handleAnimation();
};
2015-01-27 15:07:51 +08:00
Konva.Animation._removeAnimation = function(anim) {
2014-02-27 08:49:18 +08:00
var id = anim.id,
animations = this.animations,
len = animations.length,
n;
for(n = 0; n < len; n++) {
if(animations[n].id === id) {
this.animations.splice(n, 1);
break;
}
}
};
2015-01-27 15:07:51 +08:00
Konva.Animation._runFrames = function() {
2014-09-23 15:05:30 +08:00
var layerHash = {},
animations = this.animations,
anim, layers, func, n, i, layersLen, layer, key, needRedraw;
/*
* loop through all animations and execute animation
* function. if the animation object has specified node,
* we can add the node to the nodes hash to eliminate
* drawing the same node multiple times. The node property
* can be the stage itself or a layer
*/
/*
* WARNING: don't cache animations.length because it could change while
* the for loop is running, causing a JS error
*/
2014-09-23 15:05:30 +08:00
for(n = 0; n < animations.length; n++) {
anim = animations[n];
layers = anim.layers;
func = anim.func;
2014-09-23 15:05:30 +08:00
anim._updateFrameObject(now());
2013-05-12 11:07:20 +08:00
layersLen = layers.length;
2013-05-12 11:07:20 +08:00
// if animation object has a function, execute it
if (func) {
// allow anim bypassing drawing
needRedraw = (func.call(anim, anim.frame) !== false);
2014-09-23 15:05:30 +08:00
} else {
needRedraw = true;
2013-05-12 11:07:20 +08:00
}
2015-05-04 17:02:16 +08:00
if (!needRedraw) {
continue;
}
for (i = 0; i < layersLen; i++) {
layer = layers[i];
2014-09-23 15:05:30 +08:00
2015-05-04 17:02:16 +08:00
if (layer._id !== undefined) {
layerHash[layer._id] = layer;
}
}
}
2014-09-23 15:05:30 +08:00
for (key in layerHash) {
2015-10-22 13:32:07 +08:00
if (!layerHash.hasOwnProperty(key)) {
continue;
}
2014-09-23 15:05:30 +08:00
layerHash[key].draw();
}
};
2015-01-27 15:07:51 +08:00
Konva.Animation._animationLoop = function() {
var Anim = Konva.Animation;
if(Anim.animations.length) {
requestAnimFrame(Anim._animationLoop);
Anim._runFrames();
}
else {
Anim.animRunning = false;
}
};
2015-01-27 15:07:51 +08:00
Konva.Animation._handleAnimation = function() {
if(!this.animRunning) {
this.animRunning = true;
2015-10-22 13:32:07 +08:00
this._animationLoop();
}
};
2015-01-27 15:07:51 +08:00
var moveTo = Konva.Node.prototype.moveTo;
Konva.Node.prototype.moveTo = function(container) {
moveTo.call(this, container);
};
/**
* batch draw
* @method
2015-10-22 13:32:07 +08:00
* @return {Konva.Layer} this
2015-01-27 15:07:51 +08:00
* @memberof Konva.Base.prototype
*/
2015-01-27 15:07:51 +08:00
Konva.BaseLayer.prototype.batchDraw = function() {
var that = this,
2015-01-27 15:07:51 +08:00
Anim = Konva.Animation;
if (!this.batchAnim) {
this.batchAnim = new Anim(function() {
2014-02-27 08:49:18 +08:00
if (that.lastBatchDrawTime && now() - that.lastBatchDrawTime > BATCH_DRAW_STOP_TIME_DIFF) {
that.batchAnim.stop();
}
}, this);
}
this.lastBatchDrawTime = now();
if (!this.batchAnim.isRunning()) {
this.draw();
this.batchAnim.start();
}
2015-10-22 13:32:07 +08:00
return this;
};
/**
* batch draw
* @method
2015-10-22 13:32:07 +08:00
* @return {Konva.Stage} this
2015-01-27 15:07:51 +08:00
* @memberof Konva.Stage.prototype
*/
2015-01-27 15:07:51 +08:00
Konva.Stage.prototype.batchDraw = function() {
this.getChildren().each(function(layer) {
layer.batchDraw();
});
2015-10-22 13:32:07 +08:00
return this;
};
2015-05-04 17:02:16 +08:00
})(Konva);