From ce3b98ee9cad1bd7e6bbb7a477d2688eae75886c Mon Sep 17 00:00:00 2001 From: Eric Rowell Date: Thu, 26 Jul 2012 22:58:38 -0700 Subject: [PATCH] added some performance tweaks and polished up code here and there --- Thorfile | 2 +- dist/kinetic-core.js | 290 ++++++++++++++++++++++--------- dist/kinetic-core.min.js | 6 +- src/Animation.js | 16 +- src/Container.js | 4 +- src/Layer.js | 6 +- src/Shape.js | 42 ++--- src/Stage.js | 14 +- src/shapes/Rect.js | 49 +++--- src/shapes/Sprite.js | 157 +++++++++++++++++ tests/html/manualTests.html | 1 - tests/html/performanceTests.html | 1 - tests/html/unitTests.html | 1 - tests/js/manualTests.js | 2 +- tests/js/performanceTests.js | 65 +++++++ 15 files changed, 489 insertions(+), 167 deletions(-) create mode 100644 src/shapes/Sprite.js diff --git a/Thorfile b/Thorfile index 3dc38df1..6e5f2582 100644 --- a/Thorfile +++ b/Thorfile @@ -6,7 +6,7 @@ class Build < Thor "license.js", "src/Global.js", "src/Transition.js", "src/filters/Grayscale.js", "src/util/Type.js", "src/util/Canvas.js", "src/util/Class.js", "src/util/Tween.js", "src/util/Transform.js", "src/Animation.js", "src/Node.js", "src/Container.js", "src/Stage.js", "src/Layer.js", "src/Group.js", "src/Shape.js", - "src/shapes/Rect.js", "src/shapes/Ellipse.js", "src/shapes/Image.js", "src/shapes/Polygon.js", "src/shapes/Text.js", "src/shapes/Line.js", + "src/shapes/Rect.js", "src/shapes/Ellipse.js", "src/shapes/Image.js", "src/shapes/Polygon.js", "src/shapes/Text.js", "src/shapes/Line.js", "src/shapes/Sprite.js" ] desc "dev", "Concatenate all the js files into /dist/kinetic-VERSION.js." diff --git a/dist/kinetic-core.js b/dist/kinetic-core.js index 7bfa1dd0..07a29217 100644 --- a/dist/kinetic-core.js +++ b/dist/kinetic-core.js @@ -3,7 +3,7 @@ * http://www.kineticjs.com/ * Copyright 2012, Eric Rowell * Licensed under the MIT or GPL Version 2 licenses. - * Date: Jul 24 2012 + * Date: Jul 26 2012 * * Copyright (C) 2011 - 2012 by Eric Rowell * @@ -1092,7 +1092,7 @@ Kinetic.Animation = { frame: { time: 0, timeDiff: 0, - lastTime: 0 + lastTime: new Date().getTime() }, _addAnimation: function(anim) { anim.id = this.animIdCounter++; @@ -1133,16 +1133,10 @@ Kinetic.Animation = { } }, _updateFrameObject: function() { - var date = new Date(); - var time = date.getTime(); - if(this.frame.lastTime === 0) { - this.frame.lastTime = time; - } - else { - this.frame.timeDiff = time - this.frame.lastTime; - this.frame.lastTime = time; - this.frame.time += this.frame.timeDiff; - } + var time = new Date().getTime(); + this.frame.timeDiff = time - this.frame.lastTime; + this.frame.lastTime = time; + this.frame.time += this.frame.timeDiff; }, _animationLoop: function() { if(this.animations.length > 0) { @@ -2508,9 +2502,9 @@ Kinetic.Container = Kinetic.Node.extend({ child.parent = this; this.children.push(child); - var stage = child.getStage(); - if(stage === undefined) { + + if(!stage) { var go = Kinetic.Global; go.tempNodes.push(child); } @@ -3318,10 +3312,6 @@ Kinetic.Stage = Kinetic.Container.extend({ * @param {Event} evt */ _handleStageEvent: function(evt) { - var date = new Date(); - var time = date.getTime(); - this.lastEventTime = time; - var go = Kinetic.Global; if(!evt) { evt = window.event; @@ -3395,16 +3385,16 @@ Kinetic.Stage = Kinetic.Container.extend({ _mousemove: function(evt) { //throttle mousemove var throttle = this.attrs.throttle; - var date = new Date(); - var time = date.getTime(); + var time = new Date().getTime(); var timeDiff = time - this.lastEventTime; var tt = 1000 / throttle; - + if(timeDiff >= tt || throttle > 200) { this.mouseDown = false; this.mouseUp = false; this.mouseMove = true; this._handleStageEvent(evt); + this.lastEventTime = new Date().getTime(); } // start drag and drop @@ -3458,8 +3448,7 @@ Kinetic.Stage = Kinetic.Container.extend({ //throttle touchmove var that = this; var throttle = this.attrs.throttle; - var date = new Date(); - var time = date.getTime(); + var time = new Date().getTime(); var timeDiff = time - this.lastEventTime; var tt = 1000 / throttle; @@ -3468,6 +3457,7 @@ Kinetic.Stage = Kinetic.Container.extend({ that.touchEnd = false; that.touchMove = true; that._handleStageEvent(evt); + this.lastEventTime = new Date().getTime(); } // start drag and drop @@ -3811,8 +3801,7 @@ Kinetic.Layer = Kinetic.Container.extend({ */ draw: function(canvas) { var throttle = this.attrs.throttle; - var date = new Date(); - var time = date.getTime(); + var time = new Date().getTime(); var timeDiff = time - this.lastDrawTime; var tt = 1000 / throttle; @@ -3923,8 +3912,7 @@ Kinetic.Layer = Kinetic.Container.extend({ canvas = this.getCanvas(); } - var date = new Date(); - var time = date.getTime(); + var time = new Date().getTime(); this.lastDrawTime = time; // before draw handler @@ -4137,21 +4125,16 @@ Kinetic.Shape = Kinetic.Node.extend({ var go = Kinetic.Global; var appliedShadow = false; - if(this.attrs.stroke || this.attrs.strokeWidth) { - context.save(); - if(this.attrs.shadow && !this.appliedShadow) { - appliedShadow = this._applyShadow(context); - } - - var stroke = this.attrs.stroke ? this.attrs.stroke : 'black'; - var strokeWidth = this.attrs.strokeWidth ? this.attrs.strokeWidth : 2; - - context.lineWidth = strokeWidth; - context.strokeStyle = stroke; - context.stroke(context); - context.restore(); + context.save(); + if(this.attrs.shadow && !this.appliedShadow) { + appliedShadow = this._applyShadow(context); } + context.lineWidth = this.attrs.strokeWidth; + context.strokeStyle = this.attrs.stroke; + context.stroke(context); + context.restore(); + if(appliedShadow) { this.stroke(context); } @@ -4176,18 +4159,13 @@ Kinetic.Shape = Kinetic.Node.extend({ var f = null; // color fill - if( typeof fill == 'string') { - f = this.attrs.fill; - context.fillStyle = f; + if(Kinetic.Type._isString(fill)) { + context.fillStyle = fill; context.fill(context); } // pattern else if(fill.image) { var repeat = !fill.repeat ? 'repeat' : fill.repeat; - f = context.createPattern(fill.image, repeat); - - context.save(); - if(fill.scale) { context.scale(fill.scale.x, fill.scale.y); } @@ -4195,9 +4173,8 @@ Kinetic.Shape = Kinetic.Node.extend({ context.translate(fill.offset.x, fill.offset.y); } - context.fillStyle = f; + context.fillStyle = context.createPattern(fill.image, repeat); context.fill(context); - context.restore(); } // linear gradient else if(!s.radius && !e.radius) { @@ -4208,8 +4185,7 @@ Kinetic.Shape = Kinetic.Node.extend({ for(var n = 0; n < colorStops.length; n += 2) { grd.addColorStop(colorStops[n], colorStops[n + 1]); } - f = grd; - context.fillStyle = f; + context.fillStyle = grd; context.fill(context); } // radial gradient @@ -4221,13 +4197,11 @@ Kinetic.Shape = Kinetic.Node.extend({ for(var n = 0; n < colorStops.length; n += 2) { grd.addColorStop(colorStops[n], colorStops[n + 1]); } - f = grd; - context.fillStyle = f; + context.fillStyle = grd; context.fill(context); } else { - f = 'black'; - context.fillStyle = f; + context.fillStyle = 'black'; context.fill(context); } context.restore(); @@ -4524,35 +4498,34 @@ Kinetic.Rect = Kinetic.Shape.extend({ height: 0, cornerRadius: 0 }); - this.shapeType = "Rect"; - - config.drawFunc = function(context) { - context.beginPath(); - if(this.attrs.cornerRadius === 0) { - // simple rect - don't bother doing all that complicated maths stuff. - context.rect(0, 0, this.attrs.width, this.attrs.height); - } - else { - // arcTo would be nicer, but browser support is patchy (Opera) - context.moveTo(this.attrs.cornerRadius, 0); - context.lineTo(this.attrs.width - this.attrs.cornerRadius, 0); - context.arc(this.attrs.width - this.attrs.cornerRadius, this.attrs.cornerRadius, this.attrs.cornerRadius, Math.PI * 3 / 2, 0, false); - context.lineTo(this.attrs.width, this.attrs.height - this.attrs.cornerRadius); - context.arc(this.attrs.width - this.attrs.cornerRadius, this.attrs.height - this.attrs.cornerRadius, this.attrs.cornerRadius, 0, Math.PI / 2, false); - context.lineTo(this.attrs.cornerRadius, this.attrs.height); - context.arc(this.attrs.cornerRadius, this.attrs.height - this.attrs.cornerRadius, this.attrs.cornerRadius, Math.PI / 2, Math.PI, false); - context.lineTo(0, this.attrs.cornerRadius); - context.arc(this.attrs.cornerRadius, this.attrs.cornerRadius, this.attrs.cornerRadius, Math.PI, Math.PI * 3 / 2, false); - } - context.closePath(); - - this.fill(context); - this.stroke(context); - }; + config.drawFunc = this.drawFunc; // call super constructor this._super(config); }, + drawFunc: function(context) { + context.beginPath(); + if(this.attrs.cornerRadius === 0) { + // simple rect - don't bother doing all that complicated maths stuff. + context.rect(0, 0, this.attrs.width, this.attrs.height); + } + else { + // arcTo would be nicer, but browser support is patchy (Opera) + context.moveTo(this.attrs.cornerRadius, 0); + context.lineTo(this.attrs.width - this.attrs.cornerRadius, 0); + context.arc(this.attrs.width - this.attrs.cornerRadius, this.attrs.cornerRadius, this.attrs.cornerRadius, Math.PI * 3 / 2, 0, false); + context.lineTo(this.attrs.width, this.attrs.height - this.attrs.cornerRadius); + context.arc(this.attrs.width - this.attrs.cornerRadius, this.attrs.height - this.attrs.cornerRadius, this.attrs.cornerRadius, 0, Math.PI / 2, false); + context.lineTo(this.attrs.cornerRadius, this.attrs.height); + context.arc(this.attrs.cornerRadius, this.attrs.height - this.attrs.cornerRadius, this.attrs.cornerRadius, Math.PI / 2, Math.PI, false); + context.lineTo(0, this.attrs.cornerRadius); + context.arc(this.attrs.cornerRadius, this.attrs.cornerRadius, this.attrs.cornerRadius, Math.PI, Math.PI * 3 / 2, false); + } + context.closePath(); + + this.fill(context); + this.stroke(context); + }, /** * set width and height * @name setSize @@ -5461,3 +5434,160 @@ Kinetic.Node.addGettersSetters(Kinetic.Line, ['dashArray', 'lineCap', 'points']) * @name getPoints * @methodOf Kinetic.Line.prototype */ +/////////////////////////////////////////////////////////////////////// +// Sprite +/////////////////////////////////////////////////////////////////////// +/** + * Sprite constructor + * @constructor + * @augments Kinetic.Shape + * @param {Object} config + */ +Kinetic.Sprite = Kinetic.Shape.extend({ + init: function(config) { + this.setDefaultAttrs({ + index: 0, + frameRate: 17 + }); + + config.drawFunc = function(context) { + if(!!this.attrs.image) { + var anim = this.attrs.animation; + var index = this.attrs.index; + var f = this.attrs.animations[anim][index]; + + context.beginPath(); + context.rect(0, 0, f.width, f.height); + context.closePath(); + + this.drawImage(context, this.attrs.image, f.x, f.y, f.width, f.height, 0, 0, f.width, f.height); + } + }; + // call super constructor + this._super(config); + + var that = this; + this.on('animationChange.kinetic', function() { + // reset index when animation changes + that.setIndex(0); + }); + }, + /** + * start sprite animation + * @name start + * @methodOf Kinetic.Sprite.prototype + */ + start: function() { + var that = this; + var layer = this.getLayer(); + var ka = Kinetic.Animation; + + // if sprite already has an animation, remove it + if(this.anim) { + ka._removeAnimation(this.anim); + this.anim = null; + } + + /* + * animation object has no executable function because + * the updates are done with a fixed FPS with the setInterval + * below. The anim object only needs the layer reference for + * redraw + */ + this.anim = { + node: layer + }; + + /* + * adding the animation with the addAnimation + * method auto generates an id + */ + ka._addAnimation(this.anim); + + this.interval = setInterval(function() { + var index = that.attrs.index; + that._updateIndex(); + if(that.afterFrameFunc && index === that.afterFrameIndex) { + that.afterFrameFunc(); + } + }, 1000 / this.attrs.frameRate); + + ka._handleAnimation(); + }, + /** + * stop sprite animation + * @name stop + * @methodOf Kinetic.Sprite.prototype + */ + stop: function() { + var ka = Kinetic.Animation; + if(this.anim) { + ka._removeAnimation(this.anim); + this.anim = null; + } + clearInterval(this.interval); + }, + /** + * set after frame event handler + * @name afterFrame + * @methodOf Kinetic.Sprite.prototype + * @param {Integer} index frame index + * @param {Function} func function to be executed after frame has been drawn + */ + afterFrame: function(index, func) { + this.afterFrameIndex = index; + this.afterFrameFunc = func; + }, + _updateIndex: function() { + var i = this.attrs.index; + var a = this.attrs.animation; + if(i < this.attrs.animations[a].length - 1) { + this.attrs.index++; + } + else { + this.attrs.index = 0; + } + } +}); + +// add getters setters +Kinetic.Node.addGettersSetters(Kinetic.Sprite, ['animation', 'animations', 'index']); + +/** + * set animation key + * @name setAnimation + * @methodOf Kinetic.Sprite.prototype + * @param {String} anim animation key + */ + +/** + * set animations obect + * @name setAnimations + * @methodOf Kinetic.Sprite.prototype + * @param {Object} animations + */ + +/** + * set animation frame index + * @name setIndex + * @methodOf Kinetic.Sprite.prototype + * @param {Integer} index frame index + */ + +/** + * get animation key + * @name getAnimation + * @methodOf Kinetic.Sprite.prototype + */ + +/** + * get animations object + * @name getAnimations + * @methodOf Kinetic.Sprite.prototype + */ + +/** + * get animation frame index + * @name getIndex + * @methodOf Kinetic.Sprite.prototype + */ diff --git a/dist/kinetic-core.min.js b/dist/kinetic-core.min.js index 3ffdc413..5d21f3a9 100644 --- a/dist/kinetic-core.min.js +++ b/dist/kinetic-core.min.js @@ -3,7 +3,7 @@ * http://www.kineticjs.com/ * Copyright 2012, Eric Rowell * Licensed under the MIT or GPL Version 2 licenses. - * Date: Jul 24 2012 + * Date: Jul 26 2012 * * Copyright (C) 2011 - 2012 by Eric Rowell * @@ -25,5 +25,5 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -var Kinetic={};Kinetic.Filters={},Kinetic.Global={BUBBLE_WHITELIST:["mousedown","mousemove","mouseup","mouseover","mouseout","click","dblclick","touchstart","touchmove","touchend","tap","dbltap","dragstart","dragmove","dragend"],stages:[],idCounter:0,tempNodes:[],maxDragTimeInterval:20,drag:{moving:!1,offset:{x:0,y:0},lastDrawTime:0},warn:function(a){console&&console.warn&&console.warn("Kinetic warning: "+a)},_pullNodes:function(a){var b=this.tempNodes;for(var c=0;c=c.tweens.length&&c.onFinished()}}},Kinetic.Transition.prototype={start:function(){for(var a=0;a0},_getXY:function(a){if(this._isNumber(a))return{x:a,y:a};if(this._isArray(a)){if(a.length===1){var b=a[0];if(this._isNumber(b))return{x:b,y:b};if(this._isArray(b))return{x:b[0],y:b[1]};if(this._isObject(b))return b}else if(a.length>=2)return{x:a[0],y:a[1]}}else if(this._isObject(a))return a;return{x:0,y:0}},_getSize:function(a){if(this._isNumber(a))return{width:a,height:a};if(this._isArray(a))if(a.length===1){var b=a[0];if(this._isNumber(b))return{width:b,height:b};if(this._isArray(b)){if(b.length>=4)return{width:b[2],height:b[3]};if(b.length>=2)return{width:b[0],height:b[1]}}else if(this._isObject(b))return b}else{if(a.length>=4)return{width:a[2],height:a[3]};if(a.length>=2)return{width:a[0],height:a[1]}}else if(this._isObject(a))return a;return{width:0,height:0}},_getPoints:function(a){if(a===undefined)return[];if(this._isObject(a[0]))return a;var b=[];for(var c=0;cthis.getDuration()?this.looping?(this.rewind(a-this._duration),this.update(),this.broadcastMessage("onLooped",{target:this,type:"onLooped"})):(this._time=this._duration,this.update(),this.stop(),this.broadcastMessage("onFinished",{target:this,type:"onFinished"})):a<0?(this.rewind(),this.update()):(this._time=a,this.update())},getTime:function(){return this._time},setDuration:function(a){this._duration=a===null||a<=0?1e5:a},getDuration:function(){return this._duration},setPosition:function(a){this.prevPos=this._pos,this.propFunc(a),this._pos=a,this.broadcastMessage("onChanged",{target:this,type:"onChanged"})},getPosition:function(a){return a===undefined&&(a=this._time),this.func(a,this.begin,this._change,this._duration)},setFinish:function(a){this._change=a-this.begin},getFinish:function(){return this.begin+this._change},start:function(){this.rewind(),this.startEnterFrame(),this.broadcastMessage("onStarted",{target:this,type:"onStarted"})},rewind:function(a){this.stop(),this._time=a===undefined?0:a,this.fixTime(),this.update()},fforward:function(){this._time=this._duration,this.fixTime(),this.update()},update:function(){this.setPosition(this.getPosition(this._time))},startEnterFrame:function(){this.stopEnterFrame(),this.isPlaying=!0,this.onEnterFrame()},onEnterFrame:function(){this.isPlaying&&this.nextFrame()},nextFrame:function(){this.setTime((this.getTimer()-this._startTime)/1e3)},stop:function(){this.stopEnterFrame(),this.broadcastMessage("onStopped",{target:this,type:"onStopped"})},stopEnterFrame:function(){this.isPlaying=!1},continueTo:function(a,b){this.begin=this._pos,this.setFinish(a),this._duration!==undefined&&this.setDuration(b),this.start()},resume:function(){this.fixTime(),this.startEnterFrame(),this.broadcastMessage("onResumed",{target:this,type:"onResumed"})},yoyo:function(){this.continueTo(this.begin,this._time)},addListener:function(a){return this.removeListener(a),this._listeners.push(a)},removeListener:function(a){var b=this._listeners,c=b.length;while(c--)if(b[c]==a)return b.splice(c,1),!0;return!1},broadcastMessage:function(){var a=[];for(var b=0;b0){this._updateFrameObject(),this._runFrames();var a=this;requestAnimFrame(function(){a._animationLoop()})}else this.animRunning=!1,this.frame.lastTime=0},_handleAnimation:function(){var a=this;this.animRunning?this.frame.lastTime=0:(this.animRunning=!0,a._animationLoop())}},requestAnimFrame=function(a){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(a){window.setTimeout(a,1e3/60)}}(),Kinetic.Node=Kinetic.Class.extend({init:function(a){this.defaultNodeAttrs={visible:!0,listening:!0,name:undefined,alpha:1,x:0,y:0,scale:{x:1,y:1},rotation:0,offset:{x:0,y:0},dragConstraint:"none",dragBounds:{},draggable:!1},this.setDefaultAttrs(this.defaultNodeAttrs),this.eventListeners={},this.setAttrs(a),this.on("draggableChange.kinetic",function(){this._onDraggableChange()});var b=this;this.on("idChange.kinetic",function(a){var c=b.getStage();c&&(c._removeId(a.oldVal),c._addId(b))}),this.on("nameChange.kinetic",function(a){var c=b.getStage();c&&(c._removeName(a.oldVal,b._id),c._addName(b))}),this._onDraggableChange()},on:function(a,b){var c=a.split(" ");for(var d=0;d1?g[1]:"";this.eventListeners[h]||(this.eventListeners[h]=[]),this.eventListeners[h].push({name:i,handler:b})}},off:function(a){var b=a.split(" ");for(var c=0;c1){var h=f[1];for(var i=0;i0&&f[0].getLevel()<=a&&e(f)}var a=this.getLevel(),b=this.getStage(),c=this,d=0;return c.nodeType!=="Stage"&&e(c.getStage().getChildren()),d},getLevel:function(){var a=0,b=this.parent;while(b)a++,b=b.parent;return a},setPosition:function(){var a=Kinetic.Type._getXY(Array.prototype.slice.call(arguments));this.setAttrs(a)},getPosition:function(){return{x:this.attrs.x,y:this.attrs.y}},getAbsolutePosition:function(){var a=this.getAbsoluteTransform(),b=this.getOffset();return a.translate(b.x,b.y),a.getTranslation()},setAbsolutePosition:function(){var a=Kinetic.Type._getXY(Array.prototype.slice.call(arguments)),b=this._clearTransform();this.attrs.x=b.x,this.attrs.y=b.y,delete b.x,delete b.y;var c=this.getAbsoluteTransform();c.invert(),c.translate(a.x,a.y),a={x:this.attrs.x+c.getTranslation().x,y:this.attrs.y+c.getTranslation().y},this.setPosition(a.x,a.y),this._setTransform(b)},move:function(){var a=Kinetic.Type._getXY(Array.prototype.slice.call(arguments)),b=this.getX(),c=this.getY();a.x!==undefined&&(b+=a.x),a.y!==undefined&&(c+=a.y),this.setAttrs({x:b,y:c})},getRotationDeg:function(){return this.attrs.rotation*180/Math.PI},rotate:function(a){this.setAttrs({rotation:this.getRotation()+a})},rotateDeg:function(a){this.setAttrs({rotation:this.getRotation()+a*Math.PI/180})},moveToTop:function(){var a=this.index;this.parent.children.splice(a,1),this.parent.children.push(this),this.parent._setChildrenIndices()},moveUp:function(){var a=this.index;this.parent.children.splice(a,1),this.parent.children.splice(a+1,0,this),this.parent._setChildrenIndices()},moveDown:function(){var a=this.index;a>0&&(this.parent.children.splice(a,1),this.parent.children.splice(a-1,0,this),this.parent._setChildrenIndices())},moveToBottom:function(){var a=this.index;this.parent.children.splice(a,1),this.parent.children.unshift(this),this.parent._setChildrenIndices()},setZIndex:function(a){var b=this.index;this.parent.children.splice(b,1),this.parent.children.splice(a,0,this),this.parent._setChildrenIndices()},getAbsoluteAlpha:function(){var a=1,b=this;while(b.nodeType!=="Stage")a*=b.attrs.alpha,b=b.parent;return a},isDragging:function(){var a=Kinetic.Global;return a.drag.node!==undefined&&a.drag.node._id===this._id&&a.drag.moving},moveTo:function(a){var b=this.parent;b.children.splice(this.index,1),b._setChildrenIndices(),a.children.push(this),this.index=a.children.length-1,this.parent=a,a._setChildrenIndices()},getParent:function(){return this.parent},getLayer:function(){return this.nodeType==="Layer"?this:this.getParent().getLayer()},getStage:function(){return this.nodeType!=="Stage"&&this.getParent()?this.getParent().getStage():this.nodeType==="Stage"?this:undefined},simulate:function(a){this._handleEvent(a,{})},transitionTo:function(a){var b=Kinetic.Animation;this.transAnim&&(b._removeAnimation(this.transAnim),this.transAnim=null);var c=this.nodeType==="Stage"?this:this.getLayer(),d=this,e=new Kinetic.Transition(this,a),f={func:function(){e._onEnterFrame()},node:c};return this.transAnim=f,b._addAnimation(f),e.onFinished=function(){b._removeAnimation(f),d.transAnim=null,a.callback!==undefined&&a.callback(),f.node.draw()},e.start(),b._handleAnimation(),e},getAbsoluteTransform:function(){var a=new Kinetic.Transform,b=[],c=this.parent;b.unshift(this);while(c)b.unshift(c),c=c.parent;for(var d=0;d=0&&!b.cancelBubble&&this.parent&&this._handleEvent.call(this.parent,a,b)}}}),Kinetic.Node.addSetters=function(constructor,a){for(var b=0;b0)this.remove(this.children[0])},add:function(a){a._id=Kinetic.Global.idCounter++,a.index=this.children.length,a.parent=this,this.children.push(a);var b=a.getStage();if(b===undefined){var c=Kinetic.Global;c.tempNodes.push(a)}else{b._addId(a),b._addName(a);var c=Kinetic.Global;c._pullNodes(b)}return this._add!==undefined&&this._add(a),this},remove:function(a){if(a&&a.index!==undefined&&this.children[a.index]._id==a._id){var b=this.getStage();b!==undefined&&(b._removeId(a.getId()),b._removeName(a.getName(),a._id));var c=Kinetic.Global;for(var d=0;d0)a.remove(a.children[0]);this._remove!==undefined&&this._remove(a)}return this},get:function(a){var b=this.getStage(),c,d=a.slice(1);if(a.charAt(0)==="#")c=b.ids[d]!==undefined?[b.ids[d]]:[];else{if(a.charAt(0)!==".")return a==="Shape"||a==="Group"||a==="Layer"?this._getNodes(a):!1;c=b.names[d]!==undefined?b.names[d]:[]}var e=[];for(var f=0;f=0;d--){var e=c[d];if(e.getListening())if(e.nodeType==="Shape"){var f=this._detectEvent(e,b);if(f)return!0}else{var f=this._traverseChildren(e,b);if(f)return!0}}return!1},_handleStageEvent:function(a){var b=new Date,c=b.getTime();this.lastEventTime=c;var d=Kinetic.Global;a||(a=window.event),this._setMousePosition(a),this._setTouchPosition(a),this.pathCanvas.clear(),this.targetFound=!1;var e=!1;for(var f=this.children.length-1;f>=0;f--){var g=this.children[f];if(g.isVisible()&&f>=0&&g.getListening()&&this._traverseChildren(g,a)){e=!0;break}}!e&&this.mouseoutShape&&(this.mouseoutShape._handleEvent("mouseout",a),this.mouseoutShape=undefined)},_bindContentEvents:function(){var a=Kinetic.Global,b=this,c=["mousedown","mousemove","mouseup","mouseover","mouseout","touchstart","touchmove","touchend"];for(var d=0;d=f|| -b>200)this.mouseDown=!1,this.mouseUp=!1,this.mouseMove=!0,this._handleStageEvent(a);this._startDrag(a)},_mousedown:function(a){this.mouseDown=!0,this.mouseUp=!1,this.mouseMove=!1,this._handleStageEvent(a),this.attrs.draggable&&this._initDrag()},_mouseup:function(a){this.mouseDown=!1,this.mouseUp=!0,this.mouseMove=!1,this._handleStageEvent(a),this.clickStart=!1,this._endDrag(a)},_touchstart:function(a){a.preventDefault(),this.touchStart=!0,this.touchEnd=!1,this.touchMove=!1,this._handleStageEvent(a),this.attrs.draggable&&this._initDrag()},_touchend:function(a){this.touchStart=!1,this.touchEnd=!0,this.touchMove=!1,this._handleStageEvent(a),this.tapStart=!1,this._endDrag(a)},_touchmove:function(a){var b=this,c=this.attrs.throttle,d=new Date,e=d.getTime(),f=e-this.lastEventTime,g=1e3/c;if(f>=g||c>200)a.preventDefault(),b.touchEnd=!1,b.touchMove=!0,b._handleStageEvent(a);this._startDrag(a)},_setMousePosition:function(a){var b=a.offsetX||a.clientX-this._getContentPosition().left+window.pageXOffset,c=a.offsetY||a.clientY-this._getContentPosition().top+window.pageYOffset;this.mousePos={x:b,y:c}},_setTouchPosition:function(a){if(a.touches!==undefined&&a.touches.length===1){var b=a.touches[0],c=b.clientX-this._getContentPosition().left+window.pageXOffset,d=b.clientY-this._getContentPosition().top+window.pageYOffset;this.touchPos={x:c,y:d}}},_getContentPosition:function(){var a=this.content.getBoundingClientRect(),b=document.documentElement;return{top:a.top+b.scrollTop,left:a.left+b.scrollLeft}},_endDrag:function(a){var b=Kinetic.Global;b.drag.node&&b.drag.moving&&(b.drag.moving=!1,b.drag.node._handleEvent("dragend",a)),b.drag.node=undefined},_startDrag:function(a){var b=this,c=Kinetic.Global,d=c.drag.node;if(d){var e=b.getUserPosition(),f=d.attrs.dragConstraint,g=d.attrs.dragBounds,h={x:d.attrs.x,y:d.attrs.y},i={x:e.x-c.drag.offset.x,y:e.y-c.drag.offset.y};g.left!==undefined&&i.xg.right&&(i.x=g.right),g.top!==undefined&&i.yg.bottom&&(i.y=g.bottom),d.setAbsolutePosition(i),f==="horizontal"?d.attrs.y=h.y:f==="vertical"&&(d.attrs.x=h.x),c.drag.node.nodeType==="Stage"?c.drag.node.draw():c.drag.node.getLayer().draw(),c.drag.moving||(c.drag.moving=!0,c.drag.node._handleEvent("dragstart",a)),c.drag.node._handleEvent("dragmove",a)}},_buildDOM:function(){this.content=document.createElement("div"),this.content.style.position="relative",this.content.style.display="inline-block",this.content.className="kineticjs-content",this.attrs.container.appendChild(this.content),this.bufferCanvas=new Kinetic.Canvas({width:this.attrs.width,height:this.attrs.height}),this.pathCanvas=new Kinetic.Canvas({width:this.attrs.width,height:this.attrs.height}),this.pathCanvas.strip(),this._resizeDOM()},_addId:function(a){a.attrs.id!==undefined&&(this.ids[a.attrs.id]=a)},_removeId:function(a){a!==undefined&&delete this.ids[a]},_addName:function(a){var b=a.attrs.name;b!==undefined&&(this.names[b]===undefined&&(this.names[b]=[]),this.names[b].push(a))},_removeName:function(a,b){if(a!==undefined){var c=this.names[a];if(c!==undefined){for(var d=0;d=f||b>200)this._draw(a),this.drawTimeout!==undefined&&(clearTimeout(this.drawTimeout),this.drawTimeout=undefined);else if(this.drawTimeout===undefined){var g=this;this.drawTimeout=setTimeout(function(){g.draw(a)},17)}},beforeDraw:function(a){this.beforeDrawFunc=a},afterDraw:function(a){this.afterDrawFunc=a},getCanvas:function(){return this.canvas},getContext:function(){return this.canvas.context},clear:function(){this.getCanvas().clear()},toDataURL:function(a){var b,c=a&&a.mimeType?a.mimeType:null,d=a&&a.quality?a.quality:null;return a&&a.width&&a.height?b=new Kinetic.Canvas(a.width,a.height):b=this.getCanvas(),b.toDataURL(c,d)},_draw:function(a){a||(a=this.getCanvas());var b=new Date,c=b.getTime();this.lastDrawTime=c,this.beforeDrawFunc!==undefined&&this.beforeDrawFunc.call(this),this.attrs.clearBeforeDraw&&a.clear(),this.isVisible()&&(this.attrs.drawFunc!==undefined&&this.attrs.drawFunc.call(this),this._drawChildren(a)),this.afterDrawFunc!==undefined&&this.afterDrawFunc.call(this)}}),Kinetic.Node.addGettersSetters(Kinetic.Layer,["clearBeforeDraw","throttle"]),Kinetic.Group=Kinetic.Container.extend({init:function(a){this.nodeType="Group",this._super(a)},draw:function(a){this._draw(a)},_draw:function(a){this.attrs.visible&&this._drawChildren(a)}}),Kinetic.Shape=Kinetic.Node.extend({init:function(a){this.setDefaultAttrs({detectionType:"path"}),this.nodeType="Shape",this.appliedShadow=!1,this._super(a)},getContext:function(){return this.getLayer().getContext()},getCanvas:function(){return this.getLayer().getCanvas()},stroke:function(a){var b=Kinetic.Global,c=!1;if(this.attrs.stroke||this.attrs.strokeWidth){a.save(),this.attrs.shadow&&!this.appliedShadow&&(c=this._applyShadow(a));var d=this.attrs.stroke?this.attrs.stroke:"black",e=this.attrs.strokeWidth?this.attrs.strokeWidth:2;a.lineWidth=e,a.strokeStyle=d,a.stroke(a),a.restore()}c&&this.stroke(a)},fill:function(a){var b=!1,c=this.attrs.fill;if(c){a.save(),this.attrs.shadow&&!this.appliedShadow&&(b=this._applyShadow(a));var d=c.start,e=c.end,f=null;if(typeof c=="string")f=this.attrs.fill,a.fillStyle=f,a.fill(a);else if(c.image){var g=c.repeat?c.repeat:"repeat";f=a.createPattern(c.image,g),a.save(),c.scale&&a.scale(c.scale.x,c.scale.y),c.offset&&a.translate(c.offset.x,c.offset.y),a.fillStyle=f,a.fill(a),a.restore()}else if(!d.radius&&!e.radius){var h=a.createLinearGradient(d.x,d.y,e.x,e.y),i=c.colorStops;for(var j=0;j0&&d&&(this.attrs.height==="auto"||e*(c+1)this.attrs.width-this.attrs.padding*2){if(f==0)break;var i=h.lastIndexOf(" "),j=h.lastIndexOf("-"),k=Math.max(i,j);if(k>=0){g=a.splice(0,1+k).join("");break}g=a.splice(0,f).join("");break}f++,f===a.length&&(g=a.splice(0,f).join(""))}this.textWidth=Math.max(this.textWidth,this._getTextSize(g).width),g!==undefined&&(b.push(g),d=!0),c++}this.textArr=b}}),Kinetic.Node.addGettersSetters(Kinetic.Text,["fontFamily","fontSize","fontStyle","textFill","textStroke","textStrokeWidth","padding","align","lineHeight","text","width","height","cornerRadius","fill","stroke","strokeWidth","shadow"]),Kinetic.Line=Kinetic.Shape.extend({init:function(a){this.setDefaultAttrs({points:[],lineCap:"butt",dashArray:[],detectionType:"pixel"}),this.shapeType="Line",a.drawFunc=function(a){var b={};a.beginPath(),a.moveTo(this.attrs.points[0].x,this.attrs.points[0].y);for(var c=1;c0){var f=this.attrs.points[c-1].x,g=this.attrs.points[c-1].y;this._dashedLine(a,f,g,d,e,this.attrs.dashArray)}else a.lineTo(d,e)}!this.attrs.lineCap||(a.lineCap=this.attrs.lineCap),this.stroke(a)},this._super(a)},_dashedLine:function(a,b,c,d,e,f){var g=f.length,h=d-b,i=e-c,j=h>i,k=j?i/h:h/i;k>9999?k=9999:k<-9999&&(k=-9999);var l=Math.sqrt(h*h+i*i),m=0,n=!0;while(l>=.1&&m<1e4){var o=f[m++%g];o===0&&(o=.001),o>l&&(o=l);var p=Math.sqrt(o*o/(1+k*k));j?(b+=h<0&&i<0?p*-1:p,c+=h<0&&i<0?k*p*-1:k*p):(b+=h<0&&i<0?k*p*-1:k*p,c+=h<0&&i<0?p*-1:p),a[n?"lineTo":"moveTo"](b,c),l-=o,n=!n}a.moveTo(d,e)}}),Kinetic.Node.addGettersSetters(Kinetic.Line,["dashArray","lineCap","points"]); +var Kinetic={};Kinetic.Filters={},Kinetic.Global={BUBBLE_WHITELIST:["mousedown","mousemove","mouseup","mouseover","mouseout","click","dblclick","touchstart","touchmove","touchend","tap","dbltap","dragstart","dragmove","dragend"],stages:[],idCounter:0,tempNodes:[],maxDragTimeInterval:20,drag:{moving:!1,offset:{x:0,y:0},lastDrawTime:0},warn:function(a){console&&console.warn&&console.warn("Kinetic warning: "+a)},_pullNodes:function(a){var b=this.tempNodes;for(var c=0;c=c.tweens.length&&c.onFinished()}}},Kinetic.Transition.prototype={start:function(){for(var a=0;a0},_getXY:function(a){if(this._isNumber(a))return{x:a,y:a};if(this._isArray(a)){if(a.length===1){var b=a[0];if(this._isNumber(b))return{x:b,y:b};if(this._isArray(b))return{x:b[0],y:b[1]};if(this._isObject(b))return b}else if(a.length>=2)return{x:a[0],y:a[1]}}else if(this._isObject(a))return a;return{x:0,y:0}},_getSize:function(a){if(this._isNumber(a))return{width:a,height:a};if(this._isArray(a))if(a.length===1){var b=a[0];if(this._isNumber(b))return{width:b,height:b};if(this._isArray(b)){if(b.length>=4)return{width:b[2],height:b[3]};if(b.length>=2)return{width:b[0],height:b[1]}}else if(this._isObject(b))return b}else{if(a.length>=4)return{width:a[2],height:a[3]};if(a.length>=2)return{width:a[0],height:a[1]}}else if(this._isObject(a))return a;return{width:0,height:0}},_getPoints:function(a){if(a===undefined)return[];if(this._isObject(a[0]))return a;var b=[];for(var c=0;cthis.getDuration()?this.looping?(this.rewind(a-this._duration),this.update(),this.broadcastMessage("onLooped",{target:this,type:"onLooped"})):(this._time=this._duration,this.update(),this.stop(),this.broadcastMessage("onFinished",{target:this,type:"onFinished"})):a<0?(this.rewind(),this.update()):(this._time=a,this.update())},getTime:function(){return this._time},setDuration:function(a){this._duration=a===null||a<=0?1e5:a},getDuration:function(){return this._duration},setPosition:function(a){this.prevPos=this._pos,this.propFunc(a),this._pos=a,this.broadcastMessage("onChanged",{target:this,type:"onChanged"})},getPosition:function(a){return a===undefined&&(a=this._time),this.func(a,this.begin,this._change,this._duration)},setFinish:function(a){this._change=a-this.begin},getFinish:function(){return this.begin+this._change},start:function(){this.rewind(),this.startEnterFrame(),this.broadcastMessage("onStarted",{target:this,type:"onStarted"})},rewind:function(a){this.stop(),this._time=a===undefined?0:a,this.fixTime(),this.update()},fforward:function(){this._time=this._duration,this.fixTime(),this.update()},update:function(){this.setPosition(this.getPosition(this._time))},startEnterFrame:function(){this.stopEnterFrame(),this.isPlaying=!0,this.onEnterFrame()},onEnterFrame:function(){this.isPlaying&&this.nextFrame()},nextFrame:function(){this.setTime((this.getTimer()-this._startTime)/1e3)},stop:function(){this.stopEnterFrame(),this.broadcastMessage("onStopped",{target:this,type:"onStopped"})},stopEnterFrame:function(){this.isPlaying=!1},continueTo:function(a,b){this.begin=this._pos,this.setFinish(a),this._duration!==undefined&&this.setDuration(b),this.start()},resume:function(){this.fixTime(),this.startEnterFrame(),this.broadcastMessage("onResumed",{target:this,type:"onResumed"})},yoyo:function(){this.continueTo(this.begin,this._time)},addListener:function(a){return this.removeListener(a),this._listeners.push(a)},removeListener:function(a){var b=this._listeners,c=b.length;while(c--)if(b[c]==a)return b.splice(c,1),!0;return!1},broadcastMessage:function(){var a=[];for(var b=0;b0){this._updateFrameObject(),this._runFrames();var a=this;requestAnimFrame(function(){a._animationLoop()})}else this.animRunning=!1,this.frame.lastTime=0},_handleAnimation:function(){var a=this;this.animRunning?this.frame.lastTime=0:(this.animRunning=!0,a._animationLoop())}},requestAnimFrame=function(a){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(a){window.setTimeout(a,1e3/60)}}(),Kinetic.Node=Kinetic.Class.extend({init:function(a){this.defaultNodeAttrs={visible:!0,listening:!0,name:undefined,alpha:1,x:0,y:0,scale:{x:1,y:1},rotation:0,offset:{x:0,y:0},dragConstraint:"none",dragBounds:{},draggable:!1},this.setDefaultAttrs(this.defaultNodeAttrs),this.eventListeners={},this.setAttrs(a),this.on("draggableChange.kinetic",function(){this._onDraggableChange()});var b=this;this.on("idChange.kinetic",function(a){var c=b.getStage();c&&(c._removeId(a.oldVal),c._addId(b))}),this.on("nameChange.kinetic",function(a){var c=b.getStage();c&&(c._removeName(a.oldVal,b._id),c._addName(b))}),this._onDraggableChange()},on:function(a,b){var c=a.split(" ");for(var d=0;d1?g[1]:"";this.eventListeners[h]||(this.eventListeners[h]=[]),this.eventListeners[h].push({name:i,handler:b})}},off:function(a){var b=a.split(" ");for(var c=0;c1){var h=f[1];for(var i=0;i0&&f[0].getLevel()<=a&&e(f)}var a=this.getLevel(),b=this.getStage(),c=this,d=0;return c.nodeType!=="Stage"&&e(c.getStage().getChildren()),d},getLevel:function(){var a=0,b=this.parent;while(b)a++,b=b.parent;return a},setPosition:function(){var a=Kinetic.Type._getXY(Array.prototype.slice.call(arguments));this.setAttrs(a)},getPosition:function(){return{x:this.attrs.x,y:this.attrs.y}},getAbsolutePosition:function(){var a=this.getAbsoluteTransform(),b=this.getOffset();return a.translate(b.x,b.y),a.getTranslation()},setAbsolutePosition:function(){var a=Kinetic.Type._getXY(Array.prototype.slice.call(arguments)),b=this._clearTransform();this.attrs.x=b.x,this.attrs.y=b.y,delete b.x,delete b.y;var c=this.getAbsoluteTransform();c.invert(),c.translate(a.x,a.y),a={x:this.attrs.x+c.getTranslation().x,y:this.attrs.y+c.getTranslation().y},this.setPosition(a.x,a.y),this._setTransform(b)},move:function(){var a=Kinetic.Type._getXY(Array.prototype.slice.call(arguments)),b=this.getX(),c=this.getY();a.x!==undefined&&(b+=a.x),a.y!==undefined&&(c+=a.y),this.setAttrs({x:b,y:c})},getRotationDeg:function(){return this.attrs.rotation*180/Math.PI},rotate:function(a){this.setAttrs({rotation:this.getRotation()+a})},rotateDeg:function(a){this.setAttrs({rotation:this.getRotation()+a*Math.PI/180})},moveToTop:function(){var a=this.index;this.parent.children.splice(a,1),this.parent.children.push(this),this.parent._setChildrenIndices()},moveUp:function(){var a=this.index;this.parent.children.splice(a,1),this.parent.children.splice(a+1,0,this),this.parent._setChildrenIndices()},moveDown:function(){var a=this.index;a>0&&(this.parent.children.splice(a,1),this.parent.children.splice(a-1,0,this),this.parent._setChildrenIndices())},moveToBottom:function(){var a=this.index;this.parent.children.splice(a,1),this.parent.children.unshift(this),this.parent._setChildrenIndices()},setZIndex:function(a){var b=this.index;this.parent.children.splice(b,1),this.parent.children.splice(a,0,this),this.parent._setChildrenIndices()},getAbsoluteAlpha:function(){var a=1,b=this;while(b.nodeType!=="Stage")a*=b.attrs.alpha,b=b.parent;return a},isDragging:function(){var a=Kinetic.Global;return a.drag.node!==undefined&&a.drag.node._id===this._id&&a.drag.moving},moveTo:function(a){var b=this.parent;b.children.splice(this.index,1),b._setChildrenIndices(),a.children.push(this),this.index=a.children.length-1,this.parent=a,a._setChildrenIndices()},getParent:function(){return this.parent},getLayer:function(){return this.nodeType==="Layer"?this:this.getParent().getLayer()},getStage:function(){return this.nodeType!=="Stage"&&this.getParent()?this.getParent().getStage():this.nodeType==="Stage"?this:undefined},simulate:function(a){this._handleEvent(a,{})},transitionTo:function(a){var b=Kinetic.Animation;this.transAnim&&(b._removeAnimation(this.transAnim),this.transAnim=null);var c=this.nodeType==="Stage"?this:this.getLayer(),d=this,e=new Kinetic.Transition(this,a),f={func:function(){e._onEnterFrame()},node:c};return this.transAnim=f,b._addAnimation(f),e.onFinished=function(){b._removeAnimation(f),d.transAnim=null,a.callback!==undefined&&a.callback(),f.node.draw()},e.start(),b._handleAnimation(),e},getAbsoluteTransform:function(){var a=new Kinetic.Transform,b=[],c=this.parent;b.unshift(this);while(c)b.unshift(c),c=c.parent;for(var d=0;d=0&&!b.cancelBubble&&this.parent&&this._handleEvent.call(this.parent,a,b)}}}),Kinetic.Node.addSetters=function(constructor,a){for(var b=0;b0)this.remove(this.children[0])},add:function(a){a._id=Kinetic.Global.idCounter++,a.index=this.children.length,a.parent=this,this.children.push(a);var b=a.getStage();if(!b){var c=Kinetic.Global;c.tempNodes.push(a)}else{b._addId(a),b._addName(a);var c=Kinetic.Global;c._pullNodes(b)}return this._add!==undefined&&this._add(a),this},remove:function(a){if(a&&a.index!==undefined&&this.children[a.index]._id==a._id){var b=this.getStage();b!==undefined&&(b._removeId(a.getId()),b._removeName(a.getName(),a._id));var c=Kinetic.Global;for(var d=0;d0)a.remove(a.children[0]);this._remove!==undefined&&this._remove(a)}return this},get:function(a){var b=this.getStage(),c,d=a.slice(1);if(a.charAt(0)==="#")c=b.ids[d]!==undefined?[b.ids[d]]:[];else{if(a.charAt(0)!==".")return a==="Shape"||a==="Group"||a==="Layer"?this._getNodes(a):!1;c=b.names[d]!==undefined?b.names[d]:[]}var e=[];for(var f=0;f=0;d--){var e=c[d];if(e.getListening())if(e.nodeType==="Shape"){var f=this._detectEvent(e,b);if(f)return!0}else{var f=this._traverseChildren(e,b);if(f)return!0}}return!1},_handleStageEvent:function(a){var b=Kinetic.Global;a||(a=window.event),this._setMousePosition(a),this._setTouchPosition(a),this.pathCanvas.clear(),this.targetFound=!1;var c=!1;for(var d=this.children.length-1;d>=0;d--){var e=this.children[d];if(e.isVisible()&&d>=0&&e.getListening()&&this._traverseChildren(e,a)){c=!0;break}}!c&&this.mouseoutShape&&(this.mouseoutShape._handleEvent("mouseout",a),this.mouseoutShape=undefined)},_bindContentEvents:function(){var a=Kinetic.Global,b=this,c=["mousedown","mousemove","mouseup","mouseover","mouseout","touchstart","touchmove","touchend"];for(var d=0;d=e||b>200)this.mouseDown=!1,this.mouseUp=!1,this.mouseMove=!0,this._handleStageEvent(a),this.lastEventTime= +(new Date).getTime();this._startDrag(a)},_mousedown:function(a){this.mouseDown=!0,this.mouseUp=!1,this.mouseMove=!1,this._handleStageEvent(a),this.attrs.draggable&&this._initDrag()},_mouseup:function(a){this.mouseDown=!1,this.mouseUp=!0,this.mouseMove=!1,this._handleStageEvent(a),this.clickStart=!1,this._endDrag(a)},_touchstart:function(a){a.preventDefault(),this.touchStart=!0,this.touchEnd=!1,this.touchMove=!1,this._handleStageEvent(a),this.attrs.draggable&&this._initDrag()},_touchend:function(a){this.touchStart=!1,this.touchEnd=!0,this.touchMove=!1,this._handleStageEvent(a),this.tapStart=!1,this._endDrag(a)},_touchmove:function(a){var b=this,c=this.attrs.throttle,d=(new Date).getTime(),e=d-this.lastEventTime,f=1e3/c;if(e>=f||c>200)a.preventDefault(),b.touchEnd=!1,b.touchMove=!0,b._handleStageEvent(a),this.lastEventTime=(new Date).getTime();this._startDrag(a)},_setMousePosition:function(a){var b=a.offsetX||a.clientX-this._getContentPosition().left+window.pageXOffset,c=a.offsetY||a.clientY-this._getContentPosition().top+window.pageYOffset;this.mousePos={x:b,y:c}},_setTouchPosition:function(a){if(a.touches!==undefined&&a.touches.length===1){var b=a.touches[0],c=b.clientX-this._getContentPosition().left+window.pageXOffset,d=b.clientY-this._getContentPosition().top+window.pageYOffset;this.touchPos={x:c,y:d}}},_getContentPosition:function(){var a=this.content.getBoundingClientRect(),b=document.documentElement;return{top:a.top+b.scrollTop,left:a.left+b.scrollLeft}},_endDrag:function(a){var b=Kinetic.Global;b.drag.node&&b.drag.moving&&(b.drag.moving=!1,b.drag.node._handleEvent("dragend",a)),b.drag.node=undefined},_startDrag:function(a){var b=this,c=Kinetic.Global,d=c.drag.node;if(d){var e=b.getUserPosition(),f=d.attrs.dragConstraint,g=d.attrs.dragBounds,h={x:d.attrs.x,y:d.attrs.y},i={x:e.x-c.drag.offset.x,y:e.y-c.drag.offset.y};g.left!==undefined&&i.xg.right&&(i.x=g.right),g.top!==undefined&&i.yg.bottom&&(i.y=g.bottom),d.setAbsolutePosition(i),f==="horizontal"?d.attrs.y=h.y:f==="vertical"&&(d.attrs.x=h.x),c.drag.node.nodeType==="Stage"?c.drag.node.draw():c.drag.node.getLayer().draw(),c.drag.moving||(c.drag.moving=!0,c.drag.node._handleEvent("dragstart",a)),c.drag.node._handleEvent("dragmove",a)}},_buildDOM:function(){this.content=document.createElement("div"),this.content.style.position="relative",this.content.style.display="inline-block",this.content.className="kineticjs-content",this.attrs.container.appendChild(this.content),this.bufferCanvas=new Kinetic.Canvas({width:this.attrs.width,height:this.attrs.height}),this.pathCanvas=new Kinetic.Canvas({width:this.attrs.width,height:this.attrs.height}),this.pathCanvas.strip(),this._resizeDOM()},_addId:function(a){a.attrs.id!==undefined&&(this.ids[a.attrs.id]=a)},_removeId:function(a){a!==undefined&&delete this.ids[a]},_addName:function(a){var b=a.attrs.name;b!==undefined&&(this.names[b]===undefined&&(this.names[b]=[]),this.names[b].push(a))},_removeName:function(a,b){if(a!==undefined){var c=this.names[a];if(c!==undefined){for(var d=0;d=e||b>200)this._draw(a),this.drawTimeout!==undefined&&(clearTimeout(this.drawTimeout),this.drawTimeout=undefined);else if(this.drawTimeout===undefined){var f=this;this.drawTimeout=setTimeout(function(){f.draw(a)},17)}},beforeDraw:function(a){this.beforeDrawFunc=a},afterDraw:function(a){this.afterDrawFunc=a},getCanvas:function(){return this.canvas},getContext:function(){return this.canvas.context},clear:function(){this.getCanvas().clear()},toDataURL:function(a){var b,c=a&&a.mimeType?a.mimeType:null,d=a&&a.quality?a.quality:null;return a&&a.width&&a.height?b=new Kinetic.Canvas(a.width,a.height):b=this.getCanvas(),b.toDataURL(c,d)},_draw:function(a){a||(a=this.getCanvas());var b=(new Date).getTime();this.lastDrawTime=b,this.beforeDrawFunc!==undefined&&this.beforeDrawFunc.call(this),this.attrs.clearBeforeDraw&&a.clear(),this.isVisible()&&(this.attrs.drawFunc!==undefined&&this.attrs.drawFunc.call(this),this._drawChildren(a)),this.afterDrawFunc!==undefined&&this.afterDrawFunc.call(this)}}),Kinetic.Node.addGettersSetters(Kinetic.Layer,["clearBeforeDraw","throttle"]),Kinetic.Group=Kinetic.Container.extend({init:function(a){this.nodeType="Group",this._super(a)},draw:function(a){this._draw(a)},_draw:function(a){this.attrs.visible&&this._drawChildren(a)}}),Kinetic.Shape=Kinetic.Node.extend({init:function(a){this.setDefaultAttrs({detectionType:"path"}),this.nodeType="Shape",this.appliedShadow=!1,this._super(a)},getContext:function(){return this.getLayer().getContext()},getCanvas:function(){return this.getLayer().getCanvas()},stroke:function(a){var b=Kinetic.Global,c=!1;a.save(),this.attrs.shadow&&!this.appliedShadow&&(c=this._applyShadow(a)),a.lineWidth=this.attrs.strokeWidth,a.strokeStyle=this.attrs.stroke,a.stroke(a),a.restore(),c&&this.stroke(a)},fill:function(a){var b=!1,c=this.attrs.fill;if(c){a.save(),this.attrs.shadow&&!this.appliedShadow&&(b=this._applyShadow(a));var d=c.start,e=c.end,f=null;if(Kinetic.Type._isString(c))a.fillStyle=c,a.fill(a);else if(c.image){var g=c.repeat?c.repeat:"repeat";c.scale&&a.scale(c.scale.x,c.scale.y),c.offset&&a.translate(c.offset.x,c.offset.y),a.fillStyle=a.createPattern(c.image,g),a.fill(a)}else if(!d.radius&&!e.radius){var h=a.createLinearGradient(d.x,d.y,e.x,e.y),i=c.colorStops;for(var j=0;j0&&d&&(this.attrs.height==="auto"||e*(c+1)this.attrs.width-this.attrs.padding*2){if(f==0)break;var i=h.lastIndexOf(" "),j=h.lastIndexOf("-"),k=Math.max(i,j);if(k>=0){g=a.splice(0,1+k).join("");break}g=a.splice(0,f).join("");break}f++,f===a.length&&(g=a.splice(0,f).join(""))}this.textWidth=Math.max(this.textWidth,this._getTextSize(g).width),g!==undefined&&(b.push(g),d=!0),c++}this.textArr=b}}),Kinetic.Node.addGettersSetters(Kinetic.Text,["fontFamily","fontSize","fontStyle","textFill","textStroke","textStrokeWidth","padding","align","lineHeight","text","width","height","cornerRadius","fill","stroke","strokeWidth","shadow"]),Kinetic.Line=Kinetic.Shape.extend({init:function(a){this.setDefaultAttrs({points:[],lineCap:"butt",dashArray:[],detectionType:"pixel"}),this.shapeType="Line",a.drawFunc=function(a){var b={};a.beginPath(),a.moveTo(this.attrs.points[0].x,this.attrs.points[0].y);for(var c=1;c0){var f=this.attrs.points[c-1].x,g=this.attrs.points[c-1].y;this._dashedLine(a,f,g,d,e,this.attrs.dashArray)}else a.lineTo(d,e)}!this.attrs.lineCap||(a.lineCap=this.attrs.lineCap),this.stroke(a)},this._super(a)},_dashedLine:function(a,b,c,d,e,f){var g=f.length,h=d-b,i=e-c,j=h>i,k=j?i/h:h/i;k>9999?k=9999:k<-9999&&(k=-9999);var l=Math.sqrt(h*h+i*i),m=0,n=!0;while(l>=.1&&m<1e4){var o=f[m++%g];o===0&&(o=.001),o>l&&(o=l);var p=Math.sqrt(o*o/(1+k*k));j?(b+=h<0&&i<0?p*-1:p,c+=h<0&&i<0?k*p*-1:k*p):(b+=h<0&&i<0?k*p*-1:k*p,c+=h<0&&i<0?p*-1:p),a[n?"lineTo":"moveTo"](b,c),l-=o,n=!n}a.moveTo(d,e)}}),Kinetic.Node.addGettersSetters(Kinetic.Line,["dashArray","lineCap","points"]),Kinetic.Sprite=Kinetic.Shape.extend({init:function(a){this.setDefaultAttrs({index:0,frameRate:17}),a.drawFunc=function(a){if(!!this.attrs.image){var b=this.attrs.animation,c=this.attrs.index,d=this.attrs.animations[b][c];a.beginPath(),a.rect(0,0,d.width,d.height),a.closePath(),this.drawImage(a,this.attrs.image,d.x,d.y,d.width,d.height,0,0,d.width,d.height)}},this._super(a);var b=this;this.on("animationChange.kinetic",function(){b.setIndex(0)})},start:function(){var a=this,b=this.getLayer(),c=Kinetic.Animation;this.anim&&(c._removeAnimation(this.anim),this.anim=null),this.anim={node:b},c._addAnimation(this.anim),this.interval=setInterval(function(){var b=a.attrs.index;a._updateIndex(),a.afterFrameFunc&&b===a.afterFrameIndex&&a.afterFrameFunc()},1e3/this.attrs.frameRate),c._handleAnimation()},stop:function(){var a=Kinetic.Animation;this.anim&&(a._removeAnimation(this.anim),this.anim=null),clearInterval(this.interval)},afterFrame:function(a,b){this.afterFrameIndex=a,this.afterFrameFunc=b},_updateIndex:function(){var a=this.attrs.index,b=this.attrs.animation;a 0) { diff --git a/src/Container.js b/src/Container.js index 0aea5f3b..376ae47b 100644 --- a/src/Container.js +++ b/src/Container.js @@ -65,9 +65,9 @@ Kinetic.Container = Kinetic.Node.extend({ child.parent = this; this.children.push(child); - var stage = child.getStage(); - if(stage === undefined) { + + if(!stage) { var go = Kinetic.Global; go.tempNodes.push(child); } diff --git a/src/Layer.js b/src/Layer.js index be2cc784..a3765d2e 100644 --- a/src/Layer.js +++ b/src/Layer.js @@ -61,8 +61,7 @@ Kinetic.Layer = Kinetic.Container.extend({ */ draw: function(canvas) { var throttle = this.attrs.throttle; - var date = new Date(); - var time = date.getTime(); + var time = new Date().getTime(); var timeDiff = time - this.lastDrawTime; var tt = 1000 / throttle; @@ -173,8 +172,7 @@ Kinetic.Layer = Kinetic.Container.extend({ canvas = this.getCanvas(); } - var date = new Date(); - var time = date.getTime(); + var time = new Date().getTime(); this.lastDrawTime = time; // before draw handler diff --git a/src/Shape.js b/src/Shape.js index 31463ffe..48ea41c1 100644 --- a/src/Shape.js +++ b/src/Shape.js @@ -99,21 +99,16 @@ Kinetic.Shape = Kinetic.Node.extend({ var go = Kinetic.Global; var appliedShadow = false; - if(this.attrs.stroke || this.attrs.strokeWidth) { - context.save(); - if(this.attrs.shadow && !this.appliedShadow) { - appliedShadow = this._applyShadow(context); - } - - var stroke = this.attrs.stroke ? this.attrs.stroke : 'black'; - var strokeWidth = this.attrs.strokeWidth ? this.attrs.strokeWidth : 2; - - context.lineWidth = strokeWidth; - context.strokeStyle = stroke; - context.stroke(context); - context.restore(); + context.save(); + if(this.attrs.shadow && !this.appliedShadow) { + appliedShadow = this._applyShadow(context); } + context.lineWidth = this.attrs.strokeWidth; + context.strokeStyle = this.attrs.stroke; + context.stroke(context); + context.restore(); + if(appliedShadow) { this.stroke(context); } @@ -138,18 +133,13 @@ Kinetic.Shape = Kinetic.Node.extend({ var f = null; // color fill - if( typeof fill == 'string') { - f = this.attrs.fill; - context.fillStyle = f; + if(Kinetic.Type._isString(fill)) { + context.fillStyle = fill; context.fill(context); } // pattern else if(fill.image) { var repeat = !fill.repeat ? 'repeat' : fill.repeat; - f = context.createPattern(fill.image, repeat); - - context.save(); - if(fill.scale) { context.scale(fill.scale.x, fill.scale.y); } @@ -157,9 +147,8 @@ Kinetic.Shape = Kinetic.Node.extend({ context.translate(fill.offset.x, fill.offset.y); } - context.fillStyle = f; + context.fillStyle = context.createPattern(fill.image, repeat); context.fill(context); - context.restore(); } // linear gradient else if(!s.radius && !e.radius) { @@ -170,8 +159,7 @@ Kinetic.Shape = Kinetic.Node.extend({ for(var n = 0; n < colorStops.length; n += 2) { grd.addColorStop(colorStops[n], colorStops[n + 1]); } - f = grd; - context.fillStyle = f; + context.fillStyle = grd; context.fill(context); } // radial gradient @@ -183,13 +171,11 @@ Kinetic.Shape = Kinetic.Node.extend({ for(var n = 0; n < colorStops.length; n += 2) { grd.addColorStop(colorStops[n], colorStops[n + 1]); } - f = grd; - context.fillStyle = f; + context.fillStyle = grd; context.fill(context); } else { - f = 'black'; - context.fillStyle = f; + context.fillStyle = 'black'; context.fill(context); } context.restore(); diff --git a/src/Stage.js b/src/Stage.js index 96b08826..8caf453a 100644 --- a/src/Stage.js +++ b/src/Stage.js @@ -614,10 +614,6 @@ Kinetic.Stage = Kinetic.Container.extend({ * @param {Event} evt */ _handleStageEvent: function(evt) { - var date = new Date(); - var time = date.getTime(); - this.lastEventTime = time; - var go = Kinetic.Global; if(!evt) { evt = window.event; @@ -691,16 +687,16 @@ Kinetic.Stage = Kinetic.Container.extend({ _mousemove: function(evt) { //throttle mousemove var throttle = this.attrs.throttle; - var date = new Date(); - var time = date.getTime(); + var time = new Date().getTime(); var timeDiff = time - this.lastEventTime; var tt = 1000 / throttle; - + if(timeDiff >= tt || throttle > 200) { this.mouseDown = false; this.mouseUp = false; this.mouseMove = true; this._handleStageEvent(evt); + this.lastEventTime = new Date().getTime(); } // start drag and drop @@ -754,8 +750,7 @@ Kinetic.Stage = Kinetic.Container.extend({ //throttle touchmove var that = this; var throttle = this.attrs.throttle; - var date = new Date(); - var time = date.getTime(); + var time = new Date().getTime(); var timeDiff = time - this.lastEventTime; var tt = 1000 / throttle; @@ -764,6 +759,7 @@ Kinetic.Stage = Kinetic.Container.extend({ that.touchEnd = false; that.touchMove = true; that._handleStageEvent(evt); + this.lastEventTime = new Date().getTime(); } // start drag and drop diff --git a/src/shapes/Rect.js b/src/shapes/Rect.js index 4a819caf..82cdfe22 100644 --- a/src/shapes/Rect.js +++ b/src/shapes/Rect.js @@ -14,35 +14,34 @@ Kinetic.Rect = Kinetic.Shape.extend({ height: 0, cornerRadius: 0 }); - this.shapeType = "Rect"; - - config.drawFunc = function(context) { - context.beginPath(); - if(this.attrs.cornerRadius === 0) { - // simple rect - don't bother doing all that complicated maths stuff. - context.rect(0, 0, this.attrs.width, this.attrs.height); - } - else { - // arcTo would be nicer, but browser support is patchy (Opera) - context.moveTo(this.attrs.cornerRadius, 0); - context.lineTo(this.attrs.width - this.attrs.cornerRadius, 0); - context.arc(this.attrs.width - this.attrs.cornerRadius, this.attrs.cornerRadius, this.attrs.cornerRadius, Math.PI * 3 / 2, 0, false); - context.lineTo(this.attrs.width, this.attrs.height - this.attrs.cornerRadius); - context.arc(this.attrs.width - this.attrs.cornerRadius, this.attrs.height - this.attrs.cornerRadius, this.attrs.cornerRadius, 0, Math.PI / 2, false); - context.lineTo(this.attrs.cornerRadius, this.attrs.height); - context.arc(this.attrs.cornerRadius, this.attrs.height - this.attrs.cornerRadius, this.attrs.cornerRadius, Math.PI / 2, Math.PI, false); - context.lineTo(0, this.attrs.cornerRadius); - context.arc(this.attrs.cornerRadius, this.attrs.cornerRadius, this.attrs.cornerRadius, Math.PI, Math.PI * 3 / 2, false); - } - context.closePath(); - - this.fill(context); - this.stroke(context); - }; + config.drawFunc = this.drawFunc; // call super constructor this._super(config); }, + drawFunc: function(context) { + context.beginPath(); + if(this.attrs.cornerRadius === 0) { + // simple rect - don't bother doing all that complicated maths stuff. + context.rect(0, 0, this.attrs.width, this.attrs.height); + } + else { + // arcTo would be nicer, but browser support is patchy (Opera) + context.moveTo(this.attrs.cornerRadius, 0); + context.lineTo(this.attrs.width - this.attrs.cornerRadius, 0); + context.arc(this.attrs.width - this.attrs.cornerRadius, this.attrs.cornerRadius, this.attrs.cornerRadius, Math.PI * 3 / 2, 0, false); + context.lineTo(this.attrs.width, this.attrs.height - this.attrs.cornerRadius); + context.arc(this.attrs.width - this.attrs.cornerRadius, this.attrs.height - this.attrs.cornerRadius, this.attrs.cornerRadius, 0, Math.PI / 2, false); + context.lineTo(this.attrs.cornerRadius, this.attrs.height); + context.arc(this.attrs.cornerRadius, this.attrs.height - this.attrs.cornerRadius, this.attrs.cornerRadius, Math.PI / 2, Math.PI, false); + context.lineTo(0, this.attrs.cornerRadius); + context.arc(this.attrs.cornerRadius, this.attrs.cornerRadius, this.attrs.cornerRadius, Math.PI, Math.PI * 3 / 2, false); + } + context.closePath(); + + this.fill(context); + this.stroke(context); + }, /** * set width and height * @name setSize diff --git a/src/shapes/Sprite.js b/src/shapes/Sprite.js new file mode 100644 index 00000000..e52c588b --- /dev/null +++ b/src/shapes/Sprite.js @@ -0,0 +1,157 @@ +/////////////////////////////////////////////////////////////////////// +// Sprite +/////////////////////////////////////////////////////////////////////// +/** + * Sprite constructor + * @constructor + * @augments Kinetic.Shape + * @param {Object} config + */ +Kinetic.Sprite = Kinetic.Shape.extend({ + init: function(config) { + this.setDefaultAttrs({ + index: 0, + frameRate: 17 + }); + + config.drawFunc = function(context) { + if(!!this.attrs.image) { + var anim = this.attrs.animation; + var index = this.attrs.index; + var f = this.attrs.animations[anim][index]; + + context.beginPath(); + context.rect(0, 0, f.width, f.height); + context.closePath(); + + this.drawImage(context, this.attrs.image, f.x, f.y, f.width, f.height, 0, 0, f.width, f.height); + } + }; + // call super constructor + this._super(config); + + var that = this; + this.on('animationChange.kinetic', function() { + // reset index when animation changes + that.setIndex(0); + }); + }, + /** + * start sprite animation + * @name start + * @methodOf Kinetic.Sprite.prototype + */ + start: function() { + var that = this; + var layer = this.getLayer(); + var ka = Kinetic.Animation; + + // if sprite already has an animation, remove it + if(this.anim) { + ka._removeAnimation(this.anim); + this.anim = null; + } + + /* + * animation object has no executable function because + * the updates are done with a fixed FPS with the setInterval + * below. The anim object only needs the layer reference for + * redraw + */ + this.anim = { + node: layer + }; + + /* + * adding the animation with the addAnimation + * method auto generates an id + */ + ka._addAnimation(this.anim); + + this.interval = setInterval(function() { + var index = that.attrs.index; + that._updateIndex(); + if(that.afterFrameFunc && index === that.afterFrameIndex) { + that.afterFrameFunc(); + } + }, 1000 / this.attrs.frameRate); + + ka._handleAnimation(); + }, + /** + * stop sprite animation + * @name stop + * @methodOf Kinetic.Sprite.prototype + */ + stop: function() { + var ka = Kinetic.Animation; + if(this.anim) { + ka._removeAnimation(this.anim); + this.anim = null; + } + clearInterval(this.interval); + }, + /** + * set after frame event handler + * @name afterFrame + * @methodOf Kinetic.Sprite.prototype + * @param {Integer} index frame index + * @param {Function} func function to be executed after frame has been drawn + */ + afterFrame: function(index, func) { + this.afterFrameIndex = index; + this.afterFrameFunc = func; + }, + _updateIndex: function() { + var i = this.attrs.index; + var a = this.attrs.animation; + if(i < this.attrs.animations[a].length - 1) { + this.attrs.index++; + } + else { + this.attrs.index = 0; + } + } +}); + +// add getters setters +Kinetic.Node.addGettersSetters(Kinetic.Sprite, ['animation', 'animations', 'index']); + +/** + * set animation key + * @name setAnimation + * @methodOf Kinetic.Sprite.prototype + * @param {String} anim animation key + */ + +/** + * set animations obect + * @name setAnimations + * @methodOf Kinetic.Sprite.prototype + * @param {Object} animations + */ + +/** + * set animation frame index + * @name setIndex + * @methodOf Kinetic.Sprite.prototype + * @param {Integer} index frame index + */ + +/** + * get animation key + * @name getAnimation + * @methodOf Kinetic.Sprite.prototype + */ + +/** + * get animations object + * @name getAnimations + * @methodOf Kinetic.Sprite.prototype + */ + +/** + * get animation frame index + * @name getIndex + * @methodOf Kinetic.Sprite.prototype + */ \ No newline at end of file diff --git a/tests/html/manualTests.html b/tests/html/manualTests.html index 3c3b0aa0..f515116f 100644 --- a/tests/html/manualTests.html +++ b/tests/html/manualTests.html @@ -6,7 +6,6 @@ - diff --git a/tests/html/performanceTests.html b/tests/html/performanceTests.html index 645bd37a..97e2c403 100644 --- a/tests/html/performanceTests.html +++ b/tests/html/performanceTests.html @@ -6,7 +6,6 @@ - diff --git a/tests/html/unitTests.html b/tests/html/unitTests.html index 40927e4a..dc5bc98a 100644 --- a/tests/html/unitTests.html +++ b/tests/html/unitTests.html @@ -6,7 +6,6 @@ - diff --git a/tests/js/manualTests.js b/tests/js/manualTests.js index 5f4742a1..5cf4f15a 100644 --- a/tests/js/manualTests.js +++ b/tests/js/manualTests.js @@ -327,7 +327,7 @@ Test.prototype.tests = { }; imageObj.src = '../assets/darth-vader.jpg'; }, - 'EVENTS - star pixel detection': function(containerId) { + '*EVENTS - star pixel detection': function(containerId) { var stage = new Kinetic.Stage({ container: containerId, width: 578, diff --git a/tests/js/performanceTests.js b/tests/js/performanceTests.js index 7a004779..47b038ac 100644 --- a/tests/js/performanceTests.js +++ b/tests/js/performanceTests.js @@ -1,4 +1,69 @@ Test.prototype.tests = { + 'DRAWING - draw rect': function(containerId) { + var stage = new Kinetic.Stage({ + container: containerId, + width: 578, + height: 200 + }); + var layer = new Kinetic.Layer(); + + startTimer(); + console.profile(); + for(var n = 0; n < 1000; n++) { + var rect = new Kinetic.Rect({ + x: 10, + y: 10, + width: 100, + height: 100, + fill: 'yellow', + stroke: 'blue' + }); + + layer.add(rect); + } + stage.add(layer); + + console.profileEnd(); + endTimer('add and draw 1,000 Kinetic rectangles'); + + }, + '*ANIMATION - test animation frame rate': function(containerId) { + var stage = new Kinetic.Stage({ + container: containerId, + width: 578, + height: 200 + }); + var layer = new Kinetic.Layer(); + var rect = new Kinetic.Rect({ + x: 200, + y: 100, + width: 100, + height: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 4 + }); + + layer.add(rect); + stage.add(layer); + + var amplitude = 150; + var period = 1000; + // in ms + var centerX = stage.getWidth() / 2 - 100 / 2; + + stage.onFrame(function(frame) { + rect.attrs.x = amplitude * Math.sin(frame.time * 2 * Math.PI / period) + centerX; + layer.draw(); + //console.log(frame.timeDiff) + }); + + stage.start(); + + setTimeout(function() { + //stage.stop(); + }, 1000) + }, 'DRAWING - draw rect vs image from image data': function(containerId) { var stage = new Kinetic.Stage({ container: containerId,