Kinetic.Animation is now a class that can be instantiated to better represent animation objects. Rather than creating new animation objects and destroying them each time an animation is started and restarted, the same animation obect is now reused, which should help with performance

This commit is contained in:
Eric Rowell
2012-07-31 23:23:00 -07:00
parent d5eee80c0e
commit 9ad9121259
8 changed files with 214 additions and 228 deletions

208
dist/kinetic-core.js vendored
View File

@@ -1091,82 +1091,88 @@ Kinetic.Transform.prototype = {
/////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////
// Animation // Animation
/////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////
Kinetic.Animation = { Kinetic.Animation = function(config) {
animations: [], if(!config) {
animIdCounter: 0, config = {};
animRunning: false, }
frame: { for(var key in config) {
time: 0, this[key] = config[key];
timeDiff: 0, }
lastTime: new Date().getTime() this.id = Kinetic.Animation.animIdCounter++;
}, };
_addAnimation: function(anim) { Kinetic.Animation.animations = [];
anim.id = this.animIdCounter++; Kinetic.Animation.animIdCounter = 0;
this.animations.push(anim); Kinetic.Animation.animRunning = false;
}, Kinetic.Animation.frame = {
_removeAnimation: function(anim) { time: 0,
var id = anim.id; timeDiff: 0,
var animations = this.animations; lastTime: new Date().getTime()
for(var n = 0; n < animations.length; n++) { };
if(animations[n].id === id) { Kinetic.Animation._addAnimation = function(anim) {
this.animations.splice(n, 1); this.animations.push(anim);
return false; };
} Kinetic.Animation._removeAnimation = function(anim) {
var id = anim.id;
var animations = this.animations;
for(var n = 0; n < animations.length; n++) {
if(animations[n].id === id) {
this.animations.splice(n, 1);
return false;
} }
}, }
_runFrames: function() { };
var nodes = {}; Kinetic.Animation._runFrames = function() {
/* var nodes = {};
* loop through all animations and execute animation /*
* function. if the animation object has specified node, * loop through all animations and execute animation
* we can add the node to the nodes hash to eliminate * function. if the animation object has specified node,
* drawing the same node multiple times. The node property * we can add the node to the nodes hash to eliminate
* can be the stage itself or a layer * drawing the same node multiple times. The node property
*/ * can be the stage itself or a layer
for(var n = 0; n < this.animations.length; n++) { */
var anim = this.animations[n]; for(var n = 0; n < this.animations.length; n++) {
if(anim.node && anim.node._id !== undefined) { var anim = this.animations[n];
nodes[anim.node._id] = anim.node; if(anim.node && anim.node._id !== undefined) {
} nodes[anim.node._id] = anim.node;
// if animation object has a function, execute it
if(anim.func) {
anim.func(this.frame);
}
} }
// if animation object has a function, execute it
if(anim.func) {
anim.func(this.frame);
}
}
for(var key in nodes) { for(var key in nodes) {
nodes[key].draw(); nodes[key].draw();
} }
}, };
_updateFrameObject: function() { Kinetic.Animation._updateFrameObject = function() {
var time = new Date().getTime(); var time = new Date().getTime();
this.frame.timeDiff = time - this.frame.lastTime; this.frame.timeDiff = time - this.frame.lastTime;
this.frame.lastTime = time; this.frame.lastTime = time;
this.frame.time += this.frame.timeDiff; this.frame.time += this.frame.timeDiff;
}, };
_animationLoop: function() { Kinetic.Animation._animationLoop = function() {
if(this.animations.length > 0) { if(this.animations.length > 0) {
this._updateFrameObject(); this._updateFrameObject();
this._runFrames(); this._runFrames();
var that = this;
requestAnimFrame(function() {
that._animationLoop();
});
}
else {
this.animRunning = false;
this.frame.lastTime = 0;
}
},
_handleAnimation: function() {
var that = this; var that = this;
if(!this.animRunning) { requestAnimFrame(function() {
this.animRunning = true;
that._animationLoop(); that._animationLoop();
} });
else { }
this.frame.lastTime = 0; else {
} this.animRunning = false;
this.frame.lastTime = 0;
}
};
Kinetic.Animation._handleAnimation = function() {
var that = this;
if(!this.animRunning) {
this.animRunning = true;
that._animationLoop();
}
else {
this.frame.lastTime = 0;
} }
}; };
requestAnimFrame = (function(callback) { requestAnimFrame = (function(callback) {
@@ -1240,6 +1246,7 @@ Kinetic.Node = Kinetic.Class.extend({
this.setDefaultAttrs(this.defaultNodeAttrs); this.setDefaultAttrs(this.defaultNodeAttrs);
this.eventListeners = {}; this.eventListeners = {};
this.lastDragTime = 0; this.lastDragTime = 0;
this.transAnim = new Kinetic.Animation();
this.setAttrs(config); this.setAttrs(config);
// bind events // bind events
@@ -1854,10 +1861,7 @@ Kinetic.Node = Kinetic.Class.extend({
* clear transition if one is currently running for this * clear transition if one is currently running for this
* node * node
*/ */
if(this.transAnim) { a._removeAnimation(this.transAnim);
a._removeAnimation(this.transAnim);
this.transAnim = null;
}
/* /*
* create new transition * create new transition
@@ -1865,40 +1869,31 @@ Kinetic.Node = Kinetic.Class.extend({
var node = this.nodeType === 'Stage' ? this : this.getLayer(); var node = this.nodeType === 'Stage' ? this : this.getLayer();
var that = this; var that = this;
var trans = new Kinetic.Transition(this, config); var trans = new Kinetic.Transition(this, config);
var anim = {
func: function() { this.transAnim.func = function() {
trans._onEnterFrame(); trans._onEnterFrame();
},
node: node
}; };
this.transAnim.node = node;
// store reference to transition animation
this.transAnim = anim;
/* /*
* adding the animation with the addAnimation * adding the animation with the addAnimation
* method auto generates an id * method auto generates an id
*/ */
a._addAnimation(anim); a._addAnimation(this.transAnim);
// subscribe to onFinished for first tween // subscribe to onFinished for first tween
trans.onFinished = function() { trans.onFinished = function() {
// remove animation // remove animation
a._removeAnimation(anim); a._removeAnimation(that.transAnim);
that.transAnim = null; that.transAnim.node.draw();
anim.node.draw();
// callback // callback
if(config.callback) { if(config.callback) {
config.callback(); config.callback();
} }
}; };
// auto start // auto start
trans.start(); trans.start();
a._handleAnimation(); a._handleAnimation();
return trans; return trans;
}, },
/** /**
@@ -2561,7 +2556,11 @@ Kinetic.Container = Kinetic.Node.extend({
remove: function(child) { remove: function(child) {
if(child && child.index !== undefined && this.children[child.index]._id == child._id) { if(child && child.index !== undefined && this.children[child.index]._id == child._id) {
var stage = this.getStage(); var stage = this.getStage();
if(stage !== undefined) { /*
* remove event listeners and references to the node
* from the ids and names hashes
*/
if(stage) {
stage._removeId(child.getId()); stage._removeId(child.getId());
stage._removeName(child.getName(), child._id); stage._removeName(child.getName(), child._id);
} }
@@ -2783,6 +2782,7 @@ Kinetic.Stage = Kinetic.Container.extend({
go.stages.push(this); go.stages.push(this);
this._addId(this); this._addId(this);
this._addName(this); this._addName(this);
}, },
/** /**
* sets onFrameFunc for animation * sets onFrameFunc for animation
@@ -2791,9 +2791,7 @@ Kinetic.Stage = Kinetic.Container.extend({
* @param {function} func * @param {function} func
*/ */
onFrame: function(func) { onFrame: function(func) {
this.anim = { this.anim.func = func;
func: func
};
}, },
/** /**
* start animation * start animation
@@ -3686,7 +3684,7 @@ Kinetic.Stage = Kinetic.Container.extend({
this.ids = {}; this.ids = {};
this.names = {}; this.names = {};
this.anim = undefined; this.anim = new Kinetic.Animation();
this.animRunning = false; this.animRunning = false;
}, },
_draw: function(canvas) { _draw: function(canvas) {
@@ -5396,6 +5394,7 @@ Kinetic.Sprite = Kinetic.Shape.extend({
config.drawFunc = this.drawFunc; config.drawFunc = this.drawFunc;
// call super constructor // call super constructor
this._super(config); this._super(config);
this.anim = new Kinetic.Animation();
var that = this; var that = this;
this.on('animationChange.kinetic', function() { this.on('animationChange.kinetic', function() {
// reset index when animation changes // reset index when animation changes
@@ -5426,10 +5425,7 @@ Kinetic.Sprite = Kinetic.Shape.extend({
var ka = Kinetic.Animation; var ka = Kinetic.Animation;
// if sprite already has an animation, remove it // if sprite already has an animation, remove it
if(this.anim) { ka._removeAnimation(this.anim);
ka._removeAnimation(this.anim);
this.anim = null;
}
/* /*
* animation object has no executable function because * animation object has no executable function because
@@ -5437,9 +5433,7 @@ Kinetic.Sprite = Kinetic.Shape.extend({
* below. The anim object only needs the layer reference for * below. The anim object only needs the layer reference for
* redraw * redraw
*/ */
this.anim = { this.anim.node = layer;
node: layer
};
/* /*
* adding the animation with the addAnimation * adding the animation with the addAnimation
@@ -5463,11 +5457,7 @@ Kinetic.Sprite = Kinetic.Shape.extend({
* @methodOf Kinetic.Sprite.prototype * @methodOf Kinetic.Sprite.prototype
*/ */
stop: function() { stop: function() {
var ka = Kinetic.Animation; Kinetic.Animation._removeAnimation(this.anim);
if(this.anim) {
ka._removeAnimation(this.anim);
this.anim = null;
}
clearInterval(this.interval); clearInterval(this.interval);
}, },
/** /**

File diff suppressed because one or more lines are too long

View File

@@ -1,82 +1,88 @@
/////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////
// Animation // Animation
/////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////
Kinetic.Animation = { Kinetic.Animation = function(config) {
animations: [], if(!config) {
animIdCounter: 0, config = {};
animRunning: false, }
frame: { for(var key in config) {
time: 0, this[key] = config[key];
timeDiff: 0, }
lastTime: new Date().getTime() this.id = Kinetic.Animation.animIdCounter++;
}, };
_addAnimation: function(anim) { Kinetic.Animation.animations = [];
anim.id = this.animIdCounter++; Kinetic.Animation.animIdCounter = 0;
this.animations.push(anim); Kinetic.Animation.animRunning = false;
}, Kinetic.Animation.frame = {
_removeAnimation: function(anim) { time: 0,
var id = anim.id; timeDiff: 0,
var animations = this.animations; lastTime: new Date().getTime()
for(var n = 0; n < animations.length; n++) { };
if(animations[n].id === id) { Kinetic.Animation._addAnimation = function(anim) {
this.animations.splice(n, 1); this.animations.push(anim);
return false; };
} Kinetic.Animation._removeAnimation = function(anim) {
var id = anim.id;
var animations = this.animations;
for(var n = 0; n < animations.length; n++) {
if(animations[n].id === id) {
this.animations.splice(n, 1);
return false;
} }
}, }
_runFrames: function() { };
var nodes = {}; Kinetic.Animation._runFrames = function() {
/* var nodes = {};
* loop through all animations and execute animation /*
* function. if the animation object has specified node, * loop through all animations and execute animation
* we can add the node to the nodes hash to eliminate * function. if the animation object has specified node,
* drawing the same node multiple times. The node property * we can add the node to the nodes hash to eliminate
* can be the stage itself or a layer * drawing the same node multiple times. The node property
*/ * can be the stage itself or a layer
for(var n = 0; n < this.animations.length; n++) { */
var anim = this.animations[n]; for(var n = 0; n < this.animations.length; n++) {
if(anim.node && anim.node._id !== undefined) { var anim = this.animations[n];
nodes[anim.node._id] = anim.node; if(anim.node && anim.node._id !== undefined) {
} nodes[anim.node._id] = anim.node;
// if animation object has a function, execute it
if(anim.func) {
anim.func(this.frame);
}
} }
// if animation object has a function, execute it
if(anim.func) {
anim.func(this.frame);
}
}
for(var key in nodes) { for(var key in nodes) {
nodes[key].draw(); nodes[key].draw();
} }
}, };
_updateFrameObject: function() { Kinetic.Animation._updateFrameObject = function() {
var time = new Date().getTime(); var time = new Date().getTime();
this.frame.timeDiff = time - this.frame.lastTime; this.frame.timeDiff = time - this.frame.lastTime;
this.frame.lastTime = time; this.frame.lastTime = time;
this.frame.time += this.frame.timeDiff; this.frame.time += this.frame.timeDiff;
}, };
_animationLoop: function() { Kinetic.Animation._animationLoop = function() {
if(this.animations.length > 0) { if(this.animations.length > 0) {
this._updateFrameObject(); this._updateFrameObject();
this._runFrames(); this._runFrames();
var that = this;
requestAnimFrame(function() {
that._animationLoop();
});
}
else {
this.animRunning = false;
this.frame.lastTime = 0;
}
},
_handleAnimation: function() {
var that = this; var that = this;
if(!this.animRunning) { requestAnimFrame(function() {
this.animRunning = true;
that._animationLoop(); that._animationLoop();
} });
else { }
this.frame.lastTime = 0; else {
} this.animRunning = false;
this.frame.lastTime = 0;
}
};
Kinetic.Animation._handleAnimation = function() {
var that = this;
if(!this.animRunning) {
this.animRunning = true;
that._animationLoop();
}
else {
this.frame.lastTime = 0;
} }
}; };
requestAnimFrame = (function(callback) { requestAnimFrame = (function(callback) {

View File

@@ -99,7 +99,11 @@ Kinetic.Container = Kinetic.Node.extend({
remove: function(child) { remove: function(child) {
if(child && child.index !== undefined && this.children[child.index]._id == child._id) { if(child && child.index !== undefined && this.children[child.index]._id == child._id) {
var stage = this.getStage(); var stage = this.getStage();
if(stage !== undefined) { /*
* remove event listeners and references to the node
* from the ids and names hashes
*/
if(stage) {
stage._removeId(child.getId()); stage._removeId(child.getId());
stage._removeName(child.getName(), child._id); stage._removeName(child.getName(), child._id);
} }

View File

@@ -62,6 +62,7 @@ Kinetic.Node = Kinetic.Class.extend({
this.setDefaultAttrs(this.defaultNodeAttrs); this.setDefaultAttrs(this.defaultNodeAttrs);
this.eventListeners = {}; this.eventListeners = {};
this.lastDragTime = 0; this.lastDragTime = 0;
this.transAnim = new Kinetic.Animation();
this.setAttrs(config); this.setAttrs(config);
// bind events // bind events
@@ -676,10 +677,7 @@ Kinetic.Node = Kinetic.Class.extend({
* clear transition if one is currently running for this * clear transition if one is currently running for this
* node * node
*/ */
if(this.transAnim) { a._removeAnimation(this.transAnim);
a._removeAnimation(this.transAnim);
this.transAnim = null;
}
/* /*
* create new transition * create new transition
@@ -687,40 +685,31 @@ Kinetic.Node = Kinetic.Class.extend({
var node = this.nodeType === 'Stage' ? this : this.getLayer(); var node = this.nodeType === 'Stage' ? this : this.getLayer();
var that = this; var that = this;
var trans = new Kinetic.Transition(this, config); var trans = new Kinetic.Transition(this, config);
var anim = {
func: function() { this.transAnim.func = function() {
trans._onEnterFrame(); trans._onEnterFrame();
},
node: node
}; };
this.transAnim.node = node;
// store reference to transition animation
this.transAnim = anim;
/* /*
* adding the animation with the addAnimation * adding the animation with the addAnimation
* method auto generates an id * method auto generates an id
*/ */
a._addAnimation(anim); a._addAnimation(this.transAnim);
// subscribe to onFinished for first tween // subscribe to onFinished for first tween
trans.onFinished = function() { trans.onFinished = function() {
// remove animation // remove animation
a._removeAnimation(anim); a._removeAnimation(that.transAnim);
that.transAnim = null; that.transAnim.node.draw();
anim.node.draw();
// callback // callback
if(config.callback) { if(config.callback) {
config.callback(); config.callback();
} }
}; };
// auto start // auto start
trans.start(); trans.start();
a._handleAnimation(); a._handleAnimation();
return trans; return trans;
}, },
/** /**

View File

@@ -69,6 +69,7 @@ Kinetic.Stage = Kinetic.Container.extend({
go.stages.push(this); go.stages.push(this);
this._addId(this); this._addId(this);
this._addName(this); this._addName(this);
}, },
/** /**
* sets onFrameFunc for animation * sets onFrameFunc for animation
@@ -77,9 +78,7 @@ Kinetic.Stage = Kinetic.Container.extend({
* @param {function} func * @param {function} func
*/ */
onFrame: function(func) { onFrame: function(func) {
this.anim = { this.anim.func = func;
func: func
};
}, },
/** /**
* start animation * start animation
@@ -972,7 +971,7 @@ Kinetic.Stage = Kinetic.Container.extend({
this.ids = {}; this.ids = {};
this.names = {}; this.names = {};
this.anim = undefined; this.anim = new Kinetic.Animation();
this.animRunning = false; this.animRunning = false;
}, },
_draw: function(canvas) { _draw: function(canvas) {

View File

@@ -17,6 +17,7 @@ Kinetic.Sprite = Kinetic.Shape.extend({
config.drawFunc = this.drawFunc; config.drawFunc = this.drawFunc;
// call super constructor // call super constructor
this._super(config); this._super(config);
this.anim = new Kinetic.Animation();
var that = this; var that = this;
this.on('animationChange.kinetic', function() { this.on('animationChange.kinetic', function() {
// reset index when animation changes // reset index when animation changes
@@ -47,10 +48,7 @@ Kinetic.Sprite = Kinetic.Shape.extend({
var ka = Kinetic.Animation; var ka = Kinetic.Animation;
// if sprite already has an animation, remove it // if sprite already has an animation, remove it
if(this.anim) { ka._removeAnimation(this.anim);
ka._removeAnimation(this.anim);
this.anim = null;
}
/* /*
* animation object has no executable function because * animation object has no executable function because
@@ -58,9 +56,7 @@ Kinetic.Sprite = Kinetic.Shape.extend({
* below. The anim object only needs the layer reference for * below. The anim object only needs the layer reference for
* redraw * redraw
*/ */
this.anim = { this.anim.node = layer;
node: layer
};
/* /*
* adding the animation with the addAnimation * adding the animation with the addAnimation
@@ -84,11 +80,7 @@ Kinetic.Sprite = Kinetic.Shape.extend({
* @methodOf Kinetic.Sprite.prototype * @methodOf Kinetic.Sprite.prototype
*/ */
stop: function() { stop: function() {
var ka = Kinetic.Animation; Kinetic.Animation._removeAnimation(this.anim);
if(this.anim) {
ka._removeAnimation(this.anim);
this.anim = null;
}
clearInterval(this.interval); clearInterval(this.interval);
}, },
/** /**

View File

@@ -748,7 +748,6 @@ Test.prototype.tests = {
test(go.tempNodes[circle._id] === undefined, 'circle shouldn\'t be in the temp nodes hash'); test(go.tempNodes[circle._id] === undefined, 'circle shouldn\'t be in the temp nodes hash');
}, },
'CONTAINER - remove layer with shape': function(containerId) { 'CONTAINER - remove layer with shape': function(containerId) {
var stage = new Kinetic.Stage({ var stage = new Kinetic.Stage({
@@ -4500,29 +4499,36 @@ Test.prototype.tests = {
layer.draw(); layer.draw();
}); });
var a = Kinetic.Animation; var a = Kinetic.Animation;
test(a.animations.length === 0, 'should be no animations running'); test(a.animations.length === 0, 'should be no animations running');
test(stage.animRunning === false, 'animRunning should be false'); test(stage.animRunning === false, 'animRunning should be false');
stage.start(); stage.start();
test(a.animations.length === 1, 'should be 1 animation running'); test(a.animations.length === 1, 'should be 1 animation running');
test(a.animations[0].id === stage.anim.id, 'animation id is incorrect');
test(stage.animRunning === true, 'animRunning should be true');
stage.stop();
test(a.animations.length === 0, 'should be no animations running');
test(stage.animRunning === false, 'animRunning should be false');
stage.start();
test(a.animations.length === 1, 'should be 1 animation running');
test(a.animations[0].id === stage.anim.id, 'animation id is incorrect');
test(stage.animRunning === true, 'animRunning should be true'); test(stage.animRunning === true, 'animRunning should be true');
stage.start(); stage.start();
test(a.animations.length === 1, 'should be 1 animation running'); test(a.animations.length === 1, 'should be 1 animation running');
test(a.animations[0].id === stage.anim.id, 'animation id is incorrect');
test(stage.animRunning === true, 'animRunning should be true'); test(stage.animRunning === true, 'animRunning should be true');
stage.stop(); stage.stop();
test(a.animations.length === 0, 'should be no animations running'); test(a.animations.length === 0, 'should be no animations running');
test(stage.animRunning === false, 'animRunning should be false'); test(stage.animRunning === false, 'animRunning should be false');
stage.stop(); stage.stop();
test(a.animations.length === 0, 'should be no animations running'); test(a.animations.length === 0, 'should be no animations running');
test(stage.animRunning === false, 'animRunning should be false'); test(stage.animRunning === false, 'animRunning should be false');
}, },
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
// TRANSITION tests // TRANSITION tests