mirror of
https://github.com/konvajs/konva.git
synced 2025-04-05 20:48:28 +08:00
fixed several memory issues with transition logic. Heavily refactored Transition module. New Animation isRunning method. destroy() method now correctly stops currently running transitions. added several transition and destroy related unit tests
This commit is contained in:
parent
b6ba1a503c
commit
8ed84f474a
2
Thorfile
2
Thorfile
@ -12,6 +12,7 @@ class Build < Thor
|
||||
]
|
||||
|
||||
UNIT_TESTS = [
|
||||
"tests/js/unit/animationTests.js",
|
||||
"tests/js/unit/globalTests.js",
|
||||
"tests/js/unit/nodeTests.js",
|
||||
"tests/js/unit/stageTests.js",
|
||||
@ -19,7 +20,6 @@ class Build < Thor
|
||||
"tests/js/unit/layerTests.js",
|
||||
"tests/js/unit/shapeTests.js",
|
||||
"tests/js/unit/ddTests.js",
|
||||
"tests/js/unit/animationTests.js",
|
||||
"tests/js/unit/transitionTests.js",
|
||||
"tests/js/unit/shapes/rectTests.js",
|
||||
"tests/js/unit/shapes/circleTests.js",
|
||||
|
@ -4,8 +4,7 @@
|
||||
* animations
|
||||
* @constructor
|
||||
* @param {Function} func function executed on each animation frame
|
||||
* @param {Kinetic.Node} [node] node to be redrawn. Specifying a node will improve
|
||||
* draw performance. This can be a shape, a group, a layer, or the stage.
|
||||
* @param {Kinetic.Node} [node] node to be redrawn. Can be a layer or the stage. Not specifying a node will result in no redraw.
|
||||
*/
|
||||
Kinetic.Animation = function(func, node) {
|
||||
this.func = func;
|
||||
@ -21,6 +20,20 @@
|
||||
* Animation methods
|
||||
*/
|
||||
Kinetic.Animation.prototype = {
|
||||
/**
|
||||
* determine if animation is running. returns true or false
|
||||
* @name isRunning
|
||||
* @methodOf Kinetic.Aniamtion.prototype
|
||||
*/
|
||||
isRunning: function() {
|
||||
var a = Kinetic.Animation, animations = a.animations;
|
||||
for(var n = 0; n < animations.length; n++) {
|
||||
if(animations[n].id === this.id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* start animation
|
||||
* @name start
|
||||
|
@ -26,7 +26,7 @@
|
||||
// Group or Shape node types
|
||||
else {
|
||||
if(this.getDragOnTop()) {
|
||||
this._buildDragLayer();
|
||||
dd._buildDragLayer(this);
|
||||
dd.anim.node = stage.dragLayer;
|
||||
dd.prevParent = this.getParent();
|
||||
// WARNING: it's important to delay the moveTo operation,
|
||||
@ -34,10 +34,12 @@
|
||||
// has completed or else there will be a flicker on mobile devices
|
||||
// due to the time it takes to append the dd canvas to the DOM
|
||||
setTimeout(function() {
|
||||
that.moveTo(stage.dragLayer);
|
||||
dd.prevParent.getLayer().draw();
|
||||
stage.dragLayer.draw();
|
||||
dd.anim.start();
|
||||
if(dd.node) {
|
||||
that.moveTo(stage.dragLayer);
|
||||
dd.prevParent.getLayer().draw();
|
||||
stage.dragLayer.draw();
|
||||
dd.anim.start();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
else {
|
||||
@ -47,11 +49,11 @@
|
||||
}
|
||||
}
|
||||
};
|
||||
Kinetic.Node.prototype._buildDragLayer = function() {
|
||||
var dd = Kinetic.DD, stage = this.getStage(), nodeType = this.nodeType, lastContainer, group;
|
||||
Kinetic.DD._buildDragLayer = function(no) {
|
||||
var dd = Kinetic.DD, stage = no.getStage(), nodeType = no.nodeType, lastContainer, group;
|
||||
|
||||
// re-construct node tree
|
||||
this._eachAncestorReverse(function(node) {
|
||||
no._eachAncestorReverse(function(node) {
|
||||
if(node.nodeType === 'Layer') {
|
||||
stage.dragLayer.setAttrs({
|
||||
x: node.getX(),
|
||||
@ -112,9 +114,9 @@
|
||||
}
|
||||
};
|
||||
Kinetic.DD._endDrag = function(evt) {
|
||||
var dd = Kinetic.DD, node = dd.node;
|
||||
if(node) {
|
||||
var nodeType = node.nodeType, stage = node.getStage();
|
||||
var dd = Kinetic.DD, node = dd.node, nodeType, stage;
|
||||
|
||||
if(node) { nodeType = node.nodeType, stage = node.getStage();
|
||||
node.setListening(true);
|
||||
if(nodeType === 'Stage') {
|
||||
node.draw();
|
||||
@ -122,7 +124,7 @@
|
||||
else {
|
||||
if((nodeType === 'Group' || nodeType === 'Shape') && node.getDragOnTop() && dd.prevParent) {
|
||||
node.moveTo(dd.prevParent);
|
||||
stage.dragLayer.remove();
|
||||
node.getStage().dragLayer.remove();
|
||||
dd.prevParent = null;
|
||||
}
|
||||
|
||||
@ -137,7 +139,7 @@
|
||||
node._handleEvent('dragend', evt);
|
||||
}
|
||||
}
|
||||
dd.node = null;
|
||||
delete dd.node;
|
||||
dd.anim.stop();
|
||||
};
|
||||
/**
|
||||
|
13
src/Node.js
13
src/Node.js
@ -152,8 +152,6 @@
|
||||
destroy: function() {
|
||||
var parent = this.getParent(), stage = this.getStage(), dd = Kinetic.DD, go = Kinetic.Global;
|
||||
|
||||
this.remove();
|
||||
|
||||
// destroy children
|
||||
while(this.children && this.children.length > 0) {
|
||||
this.children[0].destroy();
|
||||
@ -163,10 +161,17 @@
|
||||
go._removeId(this.getId());
|
||||
go._removeName(this.getName(), this._id);
|
||||
|
||||
// remove from DD
|
||||
// stop DD
|
||||
if(dd && dd.node && dd.node._id === this._id) {
|
||||
delete Kinetic.DD.node;
|
||||
node._endDrag();
|
||||
}
|
||||
|
||||
// stop transition
|
||||
if(this.trans) {
|
||||
this.trans.stop();
|
||||
}
|
||||
|
||||
this.remove();
|
||||
},
|
||||
/**
|
||||
* get attrs
|
||||
|
35
src/Stage.js
35
src/Stage.js
@ -158,11 +158,6 @@
|
||||
getUserPosition: function() {
|
||||
return this.getTouchPosition() || this.getMousePosition();
|
||||
},
|
||||
/**
|
||||
* get stage
|
||||
* @name getStage
|
||||
* @methodOf Kinetic.Stage.prototype
|
||||
*/
|
||||
getStage: function() {
|
||||
return this;
|
||||
},
|
||||
@ -416,9 +411,13 @@
|
||||
},
|
||||
_mouseup: function(evt) {
|
||||
this._setUserPosition(evt);
|
||||
var dd = Kinetic.DD;
|
||||
var obj = this.getIntersection(this.getUserPosition());
|
||||
var that = this;
|
||||
var that = this, dd = Kinetic.DD, obj = this.getIntersection(this.getUserPosition());
|
||||
|
||||
// end drag and drop
|
||||
if(dd) {
|
||||
dd._endDrag(evt);
|
||||
}
|
||||
|
||||
if(obj && obj.shape) {
|
||||
var shape = obj.shape;
|
||||
shape._handleEvent('mouseup', evt);
|
||||
@ -443,11 +442,6 @@
|
||||
}
|
||||
}
|
||||
this.clickStart = false;
|
||||
|
||||
// end drag and drop
|
||||
if(dd) {
|
||||
dd._endDrag(evt);
|
||||
}
|
||||
},
|
||||
_touchstart: function(evt) {
|
||||
this._setUserPosition(evt);
|
||||
@ -469,9 +463,13 @@
|
||||
},
|
||||
_touchend: function(evt) {
|
||||
this._setUserPosition(evt);
|
||||
var dd = Kinetic.DD;
|
||||
var obj = this.getIntersection(this.getUserPosition());
|
||||
var that = this;
|
||||
var that = this, dd = Kinetic.DD, obj = this.getIntersection(this.getUserPosition());
|
||||
|
||||
// end drag and drop
|
||||
if(dd) {
|
||||
dd._endDrag(evt);
|
||||
}
|
||||
|
||||
if(obj && obj.shape) {
|
||||
var shape = obj.shape;
|
||||
shape._handleEvent('touchend', evt);
|
||||
@ -497,11 +495,6 @@
|
||||
}
|
||||
|
||||
this.tapStart = false;
|
||||
|
||||
// end drag and drop
|
||||
if(dd) {
|
||||
dd._endDrag(evt);
|
||||
}
|
||||
},
|
||||
_touchmove: function(evt) {
|
||||
this._setUserPosition(evt);
|
||||
|
@ -6,10 +6,11 @@
|
||||
* @constructor
|
||||
*/
|
||||
Kinetic.Transition = function(node, config) {
|
||||
var that = this, obj = {};
|
||||
|
||||
this.node = node;
|
||||
this.config = config;
|
||||
this.tweens = [];
|
||||
var that = this;
|
||||
|
||||
// add tween for each property
|
||||
function addTween(c, attrs, obj, rootObj) {
|
||||
@ -26,19 +27,38 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
var obj = {};
|
||||
addTween(config, node.attrs, obj, obj);
|
||||
|
||||
var finishedTweens = 0;
|
||||
for(var n = 0; n < this.tweens.length; n++) {
|
||||
var tween = this.tweens[n];
|
||||
tween.onFinished = function() {
|
||||
finishedTweens++;
|
||||
if(finishedTweens >= that.tweens.length) {
|
||||
that.onFinished();
|
||||
// map first tween event to transition event
|
||||
this.tweens[0].onStarted = function() {
|
||||
|
||||
};
|
||||
this.tweens[0].onStopped = function() {
|
||||
node.transAnim.stop();
|
||||
};
|
||||
this.tweens[0].onResumed = function() {
|
||||
node.transAnim.start();
|
||||
};
|
||||
this.tweens[0].onLooped = function() {
|
||||
|
||||
};
|
||||
this.tweens[0].onChanged = function() {
|
||||
|
||||
};
|
||||
this.tweens[0].onFinished = function() {
|
||||
var newAttrs = {};
|
||||
// create new attr obj
|
||||
for(var key in config) {
|
||||
if(key !== 'duration' && key !== 'easing' && key !== 'callback') {
|
||||
newAttrs[key] = config[key];
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
node.transAnim.stop();
|
||||
node.setAttrs(newAttrs);
|
||||
if(config.callback) {
|
||||
config.callback();
|
||||
}
|
||||
};
|
||||
};
|
||||
/*
|
||||
* Transition methods
|
||||
@ -116,34 +136,20 @@
|
||||
* transition completes
|
||||
*/
|
||||
Kinetic.Node.prototype.transitionTo = function(config) {
|
||||
var that = this, trans = new Kinetic.Transition(this, config);
|
||||
|
||||
if(!this.transAnim) {
|
||||
this.transAnim = new Kinetic.Animation();
|
||||
}
|
||||
/*
|
||||
* create new transition
|
||||
*/
|
||||
var node = this.nodeType === 'Stage' ? this : this.getLayer();
|
||||
var that = this;
|
||||
var trans = new Kinetic.Transition(this, config);
|
||||
|
||||
this.transAnim.func = function() {
|
||||
trans._onEnterFrame();
|
||||
};
|
||||
this.transAnim.node = node;
|
||||
this.transAnim.node = this.nodeType === 'Stage' ? this : this.getLayer();
|
||||
|
||||
// subscribe to onFinished for first tween
|
||||
trans.onFinished = function() {
|
||||
// remove animation
|
||||
that.transAnim.stop();
|
||||
|
||||
// callback
|
||||
if(config.callback) {
|
||||
config.callback();
|
||||
}
|
||||
};
|
||||
// auto start
|
||||
trans.start();
|
||||
this.transAnim.start();
|
||||
this.trans = trans;
|
||||
return trans;
|
||||
};
|
||||
})();
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,5 +1,5 @@
|
||||
Test.Modules.DD = {
|
||||
'remove shape from onclick': function(containerId) {
|
||||
'remove shape with onclick': function(containerId) {
|
||||
var stage = new Kinetic.Stage({
|
||||
container: containerId,
|
||||
width: 578,
|
||||
@ -37,6 +37,8 @@ Test.Modules.DD = {
|
||||
clientX: 291,
|
||||
clientY: 112 + top
|
||||
});
|
||||
|
||||
warn(layer.toDataURL() === dataUrls['cleared'], 'canvas should be cleared after removing shape onclick');
|
||||
|
||||
},
|
||||
'test dragstart, dragmove, dragend': function(containerId) {
|
||||
|
@ -83,14 +83,9 @@ Test.Modules.MANUAL = {
|
||||
fill: 'green',
|
||||
stroke: 'black',
|
||||
strokeWidth: 4,
|
||||
shadow: {
|
||||
color: 'black',
|
||||
offset: {
|
||||
x: 10,
|
||||
y: 10
|
||||
},
|
||||
alpha: 0.5
|
||||
}
|
||||
shadowColor: 'black',
|
||||
shadowOffset: 10,
|
||||
shadowOpacity: 0.5
|
||||
});
|
||||
|
||||
layer.add(rect);
|
||||
@ -98,16 +93,15 @@ Test.Modules.MANUAL = {
|
||||
|
||||
rect.transitionTo({
|
||||
duration: 2,
|
||||
shadow: {
|
||||
offset: {
|
||||
x: 80
|
||||
}
|
||||
shadowOffset: {
|
||||
x: 80
|
||||
},
|
||||
x: 400,
|
||||
y: 30,
|
||||
rotation: Math.PI * 2,
|
||||
easing: 'bounce-ease-out'
|
||||
});
|
||||
|
||||
},
|
||||
'TRANSITION - all transition types': function(containerId) {
|
||||
document.getElementById(containerId).style.height = '300px';
|
||||
|
@ -1936,7 +1936,7 @@ Test.Modules.NODE = {
|
||||
stage.add(layer);
|
||||
|
||||
test(stage.getAbsoluteZIndex() === 0, 'stage abs zindex should be 0');
|
||||
console.log(layer.getAbsoluteZIndex());
|
||||
//console.log(layer.getAbsoluteZIndex());
|
||||
test(layer.getAbsoluteZIndex() === 1, 'layer abs zindex should be 1');
|
||||
test(group1.getAbsoluteZIndex() === 2, 'group1 abs zindex should be 2');
|
||||
test(group2.getAbsoluteZIndex() === 3, 'group2 abs zindex should be 3');
|
||||
@ -2282,7 +2282,7 @@ Test.Modules.NODE = {
|
||||
|
||||
layer.add(circle);
|
||||
stage.add(layer);
|
||||
|
||||
|
||||
test(stage.children.length === 1, 'stage should have 1 children');
|
||||
test(stage.get('.myLayer')[0] !== undefined, 'layer should exist');
|
||||
test(stage.get('.myCircle')[0] !== undefined, 'circle should exist');
|
||||
@ -2506,5 +2506,47 @@ Test.Modules.NODE = {
|
||||
test(go.names.myRect2 === undefined, 'rect still in hash');
|
||||
test(Kinetic.Global.shapes[circleColorKey] === undefined, 'circle color key should not be in shapes hash');
|
||||
test(Kinetic.Global.shapes[rectColorKey] === undefined, 'rect color key should not be in shapes hash');
|
||||
},
|
||||
'destroy node mid transition': function(containerId) {
|
||||
var stage = new Kinetic.Stage({
|
||||
container: containerId,
|
||||
width: 578,
|
||||
height: 200
|
||||
});
|
||||
var layer = new Kinetic.Layer();
|
||||
var rect = new Kinetic.Rect({
|
||||
x: 100,
|
||||
y: 100,
|
||||
width: 100,
|
||||
height: 50,
|
||||
fill: 'green',
|
||||
stroke: 'black',
|
||||
strokeWidth: 4,
|
||||
shadowColor: 'black',
|
||||
shadowOffset: 10,
|
||||
shadowOpacity: 0.5
|
||||
});
|
||||
|
||||
layer.add(rect);
|
||||
stage.add(layer);
|
||||
|
||||
rect.transitionTo({
|
||||
duration: 2,
|
||||
shadowOffset: {
|
||||
x: 80
|
||||
},
|
||||
x: 400,
|
||||
y: 30,
|
||||
rotation: Math.PI * 2,
|
||||
easing: 'bounce-ease-out'
|
||||
});
|
||||
|
||||
setTimeout(function() {
|
||||
test(rect.transAnim.isRunning(), 'rect trans should be running before destroying it');
|
||||
rect.destroy();
|
||||
test(!rect.transAnim.isRunning(), 'rect trans should not be running after destroying it');
|
||||
layer.draw();
|
||||
warn(layer.toDataURL() === dataUrls['cleared'], 'transitioning rectangle should have been destroyed and removed from the screen');
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
|
@ -156,5 +156,50 @@ Test.Modules.TRANSITION = {
|
||||
*/
|
||||
}
|
||||
});
|
||||
},
|
||||
'stop transition': function(containerId) {
|
||||
var stage = new Kinetic.Stage({
|
||||
container: containerId,
|
||||
width: 578,
|
||||
height: 200
|
||||
});
|
||||
var layer = new Kinetic.Layer();
|
||||
var rect = new Kinetic.Rect({
|
||||
x: 100,
|
||||
y: 100,
|
||||
width: 100,
|
||||
height: 50,
|
||||
fill: 'green',
|
||||
stroke: 'black',
|
||||
strokeWidth: 4,
|
||||
shadowColor: 'black',
|
||||
shadowOffset: 10,
|
||||
shadowOpacity: 0.5
|
||||
});
|
||||
|
||||
layer.add(rect);
|
||||
stage.add(layer);
|
||||
|
||||
var trans = rect.transitionTo({
|
||||
duration: 2,
|
||||
shadowOffset: {
|
||||
x: 80
|
||||
},
|
||||
x: 400,
|
||||
y: 30,
|
||||
rotation: Math.PI * 2,
|
||||
easing: 'bounce-ease-out'
|
||||
});
|
||||
|
||||
setTimeout(function() {
|
||||
test(rect.transAnim.isRunning(), 'rect trans should be running');
|
||||
trans.stop();
|
||||
test(!rect.transAnim.isRunning(), 'rect trans should not be running');
|
||||
}, 1000);
|
||||
|
||||
setTimeout(function() {
|
||||
trans.resume();
|
||||
test(rect.transAnim.isRunning(), 'rect trans should be running after resume');
|
||||
}, 1500);
|
||||
}
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user