summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorUlrich-Matthias Schäfer <ulima.ums@googlemail.com>2016-03-23 22:50:30 +0100
committerUlrich-Matthias Schäfer <ulima.ums@googlemail.com>2016-03-23 22:50:30 +0100
commit8205de6087bd6fe48706f598c49a111f52740c97 (patch)
tree965fd228f650007bd6ded8835885d89b2e19554b /src
parent2b26d66fc4686c249ef27b2b8ca1bb20df7bb95d (diff)
downloadsvg.js-8205de6087bd6fe48706f598c49a111f52740c97.tar.gz
svg.js-8205de6087bd6fe48706f598c49a111f52740c97.zip
Implementation new fx module
Diffstat (limited to 'src')
-rw-r--r--src/element.js3
-rw-r--r--src/event.js11
-rw-r--r--src/fxnew.js179
-rw-r--r--src/fxnew2.js813
-rw-r--r--src/group.js4
-rw-r--r--src/helpers.js4
-rw-r--r--src/matrix.js5
-rw-r--r--src/transform.js248
-rw-r--r--src/viewbox.js170
9 files changed, 1297 insertions, 140 deletions
diff --git a/src/element.js b/src/element.js
index 75d77dc..552fd5b 100644
--- a/src/element.js
+++ b/src/element.js
@@ -256,5 +256,8 @@ SVG.Element = SVG.invent({
this.dom = o
return this
}
+ , is: function(obj){
+ return is(this, obj)
+ }
}
})
diff --git a/src/event.js b/src/event.js
index 66cf6c3..e94b6e1 100644
--- a/src/event.js
+++ b/src/event.js
@@ -30,6 +30,7 @@
// Initialize listeners stack
SVG.listeners = []
SVG.handlerMap = []
+SVG.listenerId = 0
// Add event binder in the SVG namespace
SVG.on = function(node, event, listener, binding) {
@@ -45,8 +46,11 @@ SVG.on = function(node, event, listener, binding) {
SVG.listeners[index][ev] = SVG.listeners[index][ev] || {}
SVG.listeners[index][ev][ns] = SVG.listeners[index][ev][ns] || {}
+ if(!listener._svgjsListenerId)
+ listener._svgjsListenerId = ++SVG.listenerId
+
// reference listener
- SVG.listeners[index][ev][ns][listener] = l
+ SVG.listeners[index][ev][ns][listener._svgjsListenerId] = l
// add listener
node.addEventListener(ev, l, false)
@@ -59,8 +63,11 @@ SVG.off = function(node, event, listener) {
, ns = event && event.split('.')[1]
if(index == -1) return
-
+
if (listener) {
+ if(typeof listener == 'function') listener = listener._svgjsListenerId
+ if(!listener) return
+
// remove listener reference
if (SVG.listeners[index][ev] && SVG.listeners[index][ev][ns || '*']) {
// remove listener
diff --git a/src/fxnew.js b/src/fxnew.js
index 324ab53..732a366 100644
--- a/src/fxnew.js
+++ b/src/fxnew.js
@@ -38,7 +38,7 @@ SVG.FX = SVG.invent({
}
this.transforms = [
- // holds all transformations of teh form:
+ // holds all transformations of the form:
// [A, B, C] or, [A, [25, 0, 0]] where ABC are matrixes and the array represents a rotation
]
@@ -173,7 +173,7 @@ SVG.FX = SVG.invent({
this.init || this.initAnimations()
- this.timeout = setTimeout(function(){ this.startAnimFrame() }.bind(this), this.delay)
+ this.timeout = setTimeout(function(){ this.startAnimFrame() }.bind(this), this._delay)
}
return this
@@ -187,6 +187,12 @@ SVG.FX = SVG.invent({
for(i in this.animations){
// TODO: this is not a clean clone of the array. We may have some unchecked references
this.animations[i].value = (i == 'plot' ? this.target.array().value : this.target[i]())
+
+ // sometimes we get back an object and not the real value, fix this
+ if(this.animations[i].value.value){
+ this.animations[i].value = this.animations[i].value.value
+ }
+
if(this.animations[i].relative)
this.animations[i].destination.value = this.animations[i].destination.value + this.animations[i].value
}
@@ -212,22 +218,27 @@ SVG.FX = SVG.invent({
}
// resets the animation to the initial state
- // TODO: maybe rename to reset
, stop: function(){
- if(!this.active) return false
+ if(!this.active) return this
this.active = false
this.stopAnimFrame()
clearTimeout(this.timeout)
- return this.seek(0)
+ return this
+ }
+
+ // do we need this one?
+ , reset: function(){
+ return this.stop().seek(0)
}
// finish off the animation
- // TODO: does it kickoff the next animation in the queue?
- // global finish or fx specific finish?
- , finish: function(){
+ // param next: true if next animation should be started
+ , finish: function(next){
this.finished = true
- return this.stop().seek(1)
+ this.loop = false
+ if(!next)this.stop()
+ return this.seek(1)
}
// set the internal animation pointer to the specified position and updates the visualisation
@@ -245,6 +256,14 @@ SVG.FX = SVG.invent({
this._end = this._start + this._duration
return this.seek(this.pos)
}
+ // Make loopable
+ , loop: function(times, reverse) {
+ // store current loop and total loops
+ this.loop = times || true
+
+ if(reverse) return this.reverse()
+ return this
+ }
// pauses the animation
, pause: function(){
@@ -268,7 +287,7 @@ SVG.FX = SVG.invent({
, reverse: function(){
if(!this.shared.reversed){
this.shared.reversed = true
- this.seek(1-this.pos)
+ //this.seek(1-this.pos)
}
return this
}
@@ -389,11 +408,15 @@ SVG.FX = SVG.invent({
// convert current time to position
if(!ignoreTime) this.pos = this.timeToPos(+new Date)
+ if(this.pos >= 1 && (this.loop === true || (typeof this.loop == 'number' && --this.loop))){
+ return this.seek(this.pos-1)
+ }
+
if(this.shared.reversed) this.pos = 1 - this.pos
// correct position
- if(this.pos > 1) this.pos = 1
- if(this.pos < 0) this.pos = 0
+ if(this.pos > 1)this.pos = 1
+ if(this.pos < 0)this.pos = 0
// apply easing
var eased = this.easing(this.pos)
@@ -413,7 +436,7 @@ SVG.FX = SVG.invent({
this.eachAt()
// do final code when situation is finished
- if(this.pos == 1){
+ if(this.pos == 1 && !this.shared.reversed){
// stop animation callback
cancelAnimationFrame(this.animationFrame)
@@ -436,6 +459,7 @@ SVG.FX = SVG.invent({
// todo: this is more or less duplicate code. has to be removed
}else if(this.shared.reversed && this.pos == 0){
+
// stop animation callback
cancelAnimationFrame(this.animationFrame)
@@ -507,11 +531,72 @@ SVG.FX = SVG.invent({
if(this.transforms.length){
at = this.transformations
for(i in this.transforms){
- if(Array.isArray(this.transforms[i])){
+
+ // we have to deal with absolute transformations
+ // very stupid stuff
+ //if(!this.transforms[i].relative){
+
+ var a = this.transforms[i]
+
+ if(a instanceof SVG.Matrix){
+
+ if(a.relative){
+ at = at.multiply(a.at(pos))
+ }else{
+ at = at.morph(a).at(this.easing(pos))
+ }
+ continue
+ }
+
+ if(!a.relative)
+ a.undo(at.extract())
+
+ if(a instanceof SVG.Translate && this.pos == 1){
+ //console.log(at, a.at(this.easing(this.pos)), at.multiply(a.at(this.easing(this.pos))))
+ }
+
+ at = at.multiply(a.at(this.easing(this.pos)))
+ continue;
+ // get the current transformation
+ /*var current = at.extract()
+
+ var undoArgs = []
+ var absArgs = []
+
+ // collect the arguments for the method we have to call
+ for(var j in this.transforms[i].order){
+ if(this.transforms[i].args[this.transforms[i].order[j]] == null)undoArgs.push(0)
+ else undoArgs.push(current[this.transforms[i].order[j]])
+ absArgs.push(this.transforms[i].args[this.transforms[i].order[j]])
+ }
+
+
+
+ // create a new matrix and call this method with its arguments on it
+ var diff = new SVG.Matrix()
+ diff = diff[this.transforms[i].method].apply(diff, undoArgs)
+
+ // build the inverse (we just undo the changes) and apply the new absolute ones
+ diff = diff.inverse()
+ if(this.transforms[i].method == 'rotate'){
+ diff = diff.rotate(new SVG.Number().morph(absArgs[0]).at(this.easing(this.pos)), absArgs[1], absArgs[2])
+ }else{
+ diff = diff[this.transforms[i].method].apply(diff, absArgs)
+ }
+
+ // the resulting matrix is the matrix with the value set absolute
+ at = at.multiply(new SVG.Matrix().morph(diff).at(this.easing(this.pos)))
+ //this.transforms[i] = new SVG.Matrix().morph(diff)
+ continue*/
+
+ //}
+
+
+ /*if(Array.isArray(this.transforms[i])){
at = at.rotate(this.transforms[i][0].at(this.easing(this.pos)), this.transforms[i][1], this.transforms[i][2])
}else{
at = at.multiply(this.transforms[i].at(this.easing(this.pos)))
- }
+ }*/
}
target.matrix(at)
@@ -561,7 +646,7 @@ SVG.FX = SVG.invent({
, construct: {
// Get fx module or create a new one, then animate with given duration and ease
animate: function(o) {
- return (this.fx || (this.fx = new SVG.FX(this))).animate(o)
+ return (this.fx || (this.fx = new SVG.FX(this))).stop().animate(o)
}
, delay: function(delay){
return (this.fx || (this.fx = new SVG.FX(this))).animate({delay:delay})
@@ -598,7 +683,7 @@ SVG.MorphObj = SVG.invent({
SVG.extend(SVG.FX, {
// Add animatable attributes
- attr: function(a, v) {
+ attr: function(a, v, relative) {
// apply attributes individually
if (typeof a == 'object') {
for (var key in a)
@@ -609,41 +694,26 @@ SVG.extend(SVG.FX, {
var from = this.search('attr', a)
// detect format
- if (a == 'transform') {
- // merge given transformation with an existing one
- //if (this.attrs[a])
- // v = this.attrs[a].multiply(v)
+ /*if (a == 'transform') {
+ // we have to calculate the rotation on our own to make sure we animate it correctly.
+ // thats why we just add the parameters instead of the matrix
if(v.rotation && !v.a){
this.transforms.push([new SVG.Number(0).morph(v.rotation), v.cx || 0, v.cy || 0])
this.start()
}else{
- this.transforms.push(new SVG.Matrix(this.target).morph(v))
+ if(!relative){
+ this.transforms.push(v)
+ }else{
+ this.transforms.push(new SVG.Matrix().morph(v))
+ }
this.start()
}
- // prepare matrix for morphing
- //this.push(a, (new SVG.Matrix(this.target)).morph(v), 'transforms')
-
- // add parametric rotation values
- /*if (this.param) {
- // get initial rotation
- v = this.target.transform('rotation')
-
- // add param
- this.attrs[a].param = {
- from: this.target.param || { rotation: v, cx: this.param.cx, cy: this.param.cy }
- , to: this.param
- }
- }*/
-
- } else {
- /*if(typeof this[a] == 'function'){
- return this[a](v)
- }*/
-
+ } else {*/
+ // FIXME: from is obsolete
this.push(a, new SVG.MorphObj(from, v), 'attrs')
- }
+ //}
}
return this
@@ -661,13 +731,25 @@ SVG.extend(SVG.FX, {
}
// Animatable x-axis
, x: function(x, relative) {
- var num = new SVG.Number(/*this.search('x')*/0).morph(x)
+ if(this.target instanceof SVG.G){
+ this.transform({x:x}, relative)
+ return this
+ }
+
+ var num = new SVG.Number(/*this.search('x')*/).morph(x)
+ //var num = new SVG.Number(this.target.x()).morph(x)
num.relative = relative
return this.push('x', num)
}
// Animatable y-axis
, y: function(y, relative) {
+ if(this.target instanceof SVG.G){
+ this.transform({y:y}, relative)
+ return this
+ }
+
var num = new SVG.Number(/*this.search('y')*/).morph(y)
+ //var num = new SVG.Number(this.target.y()).morph(y)
num.relative = relative
return this.push('y', num)
}
@@ -734,4 +816,13 @@ SVG.extend(SVG.FX, {
return this
}
+, update: function(o) {
+ if (this.target instanceof SVG.Stop) {
+ if (o.opacity != null) this.attr('stop-opacity', o.opacity)
+ if (o.color != null) this.attr('stop-color', o.color)
+ if (o.offset != null) this.attr('offset', new SVG.Number(o.offset))
+ }
+
+ return this
+ }
}) \ No newline at end of file
diff --git a/src/fxnew2.js b/src/fxnew2.js
new file mode 100644
index 0000000..7514a40
--- /dev/null
+++ b/src/fxnew2.js
@@ -0,0 +1,813 @@
+SVG.easing = {
+ '-': function(pos){return pos}
+, '<>':function(pos){return -Math.cos(pos * Math.PI) / 2 + 0.5}
+, '>': function(pos){return Math.sin(pos * Math.PI / 2)}
+, '<': function(pos){return -Math.cos(pos * Math.PI / 2) + 1}
+}
+
+var someVar = 0
+
+SVG.Situation = SVG.invent({
+
+ create: function(o){
+ this.init = false
+ this.reversed = false
+
+ this.duration = o.duration
+ this.delay = o.delay
+
+ this.start = +new Date() + this.delay
+ this.end = this.start + this.duration
+ this.easing = o.easing
+
+ this.animations = {
+ // functionToCall: [list of morphable objects]
+ // e.g. move: [SVG.Number, SVG.Number]
+ }
+
+ this.attrs = {
+ // holds all attributes which are not represented from a function svg.js provides
+ // e.g. someAttr: SVG.Number
+ }
+
+ this.styles = {
+ // holds all styles which should be animated
+ // e.g. fill-color: SVG.Color
+ }
+
+ this.transforms = [
+ // holds all transformations of the form:
+ // [A, B, C] or, [A, [25, 0, 0]] where ABC are matrixes and the array represents a rotation
+ ]
+
+ this.once = {
+ // functions to fire at a specific position
+ // e.g. "0.5": function foo(){}
+ }
+
+ }
+
+})
+
+SVG.Delay = function(delay){
+ this.delay = delay
+}
+
+SVG.FX = SVG.invent({
+
+ create: function(element) {
+ this._target = element
+ this.situations = []
+ this.active = false
+ this.current = null
+ this.paused = false
+ this.lastPos = 0
+ this.pos = 0
+ }
+
+, extend: {
+
+ /**
+ * sets or returns the target of this animation
+ * @param o object || number In case of Object it holds all parameters. In case of number its the duration of the animation
+ * @param easing function || string Function which should be used for easing or easing keyword
+ * @param delay Number indicating the delay before the animation starts
+ * @return target || this
+ */
+ animate: function(o, easing, delay){
+
+ if(typeof o == 'object'){
+ easing = o.ease
+ delay = o.delay
+ o = o.duration
+ }
+
+ var situation = new SVG.Situation({
+ duration: o || 1000,
+ delay: delay || 0,
+ easing: SVG.easing[easing || '-'] || easing
+ })
+
+ this.queue(situation)
+
+ return this
+ }
+
+ /**
+ * sets a delay before the next element of the queue is called
+ * @param delay Duration of delay in milliseconds
+ * @return this.target()
+ */
+ // FIXME: the function needs to get a delay property to make sure, that the totalProgress can be calculated
+ , delay: function(delay){
+ var delay = new SVG.Delay(delay)
+
+ return this.queue(delay)
+ }
+
+ /**
+ * sets or returns the target of this animation
+ * @param null || target SVG.Elemenet which should be set as new target
+ * @return target || this
+ */
+ , target: function(target){
+ if(target && target instanceof SVG.Element){
+ this._target = target
+ return this
+ }
+
+ return this._target
+ }
+
+ // returns the position at a given time
+ , timeToPos: function(timestamp){
+ return (timestamp - this.current.start) / (this.current.duration)
+ }
+
+ // returns the timestamp from a given positon
+ , posToTime: function(pos){
+ return this.current.duration * pos + this.current.start
+ }
+
+ // starts the animationloop
+ // TODO: It may be enough to call just this.step()
+ , startAnimFrame: function(){
+ this.stopAnimFrame()
+ this.animationFrame = requestAnimationFrame(function(){ this.step() }.bind(this))
+ }
+
+ // cancels the animationframe
+ // TODO: remove this in favour of the oneliner
+ , stopAnimFrame: function(){
+ cancelAnimationFrame(this.animationFrame)
+ }
+
+ // kicks off the animation - only does something when the queue is curretly not active and at least one situation is set
+ , start: function(){
+ // dont start if already started
+ if(!this.active && this.current){
+ this.current.start = +new Date + this.current.delay
+ this.current.end = this.current.start + this.current.duration
+
+ this.initAnimations()
+ this.active = true
+ this.startAnimFrame()
+ }
+
+ return this
+ }
+
+ /**
+ * adds a function / Situation to the animation queue
+ * @param fn function / situation to add
+ * @return this
+ */
+ , queue: function(fn){
+ if(typeof fn == 'function' || fn instanceof SVG.Situation || fn instanceof SVG.Delay)
+ this.situations.push(fn)
+
+ if(!this.current) this.current = this.situations.shift()
+
+ return this
+ }
+
+ /**
+ * pulls next element from the queue and execute it
+ * @return this
+ */
+ , dequeue: function(){
+ // stop current animation
+ this.current && this.current.stop && this.current.stop()
+
+ // get next animation from queue
+ this.current = this.situations.shift()
+
+ if(this.current){
+
+ var fn = function(){
+ if(this.current instanceof SVG.Situation)
+ this.initAnimations().seek(0)
+ else if(this.current instanceof SVG.Delay)
+ this.dequeue()
+ else
+ this.current.call(this)
+ }.bind(this)
+
+ // start next animation
+ if(this.current.delay){
+ setTimeout(function(){fn()}, this.current.delay)
+ }else{
+ fn()
+ }
+
+ }
+
+ return this
+ }
+
+ // updates all animations to the current state of the element
+ // this is important when one property could be changed from another property
+ , initAnimations: function() {
+ var i
+ var s = this.current
+
+ if(s.init) return this
+
+ for(i in s.animations){
+
+ if(i == 'viewbox'){
+ s.animations[i] = this.target().viewbox().morph(s.animations[i])
+ }else{
+
+ // TODO: this is not a clean clone of the array. We may have some unchecked references
+ s.animations[i].value = (i == 'plot' ? this.target().array().value : this.target()[i]())
+
+ // sometimes we get back an object and not the real value, fix this
+ if(s.animations[i].value.value){
+ s.animations[i].value = s.animations[i].value.value
+ }
+
+ if(s.animations[i].relative)
+ s.animations[i].destination.value = s.animations[i].destination.value + s.animations[i].value
+
+ }
+
+ }
+
+ for(i in s.attrs){
+ if(s.attrs[i] instanceof SVG.Color){
+ var color = new SVG.Color(this.target().attr(i))
+ s.attrs[i].r = color.r
+ s.attrs[i].g = color.g
+ s.attrs[i].b = color.b
+ }else{
+ s.attrs[i].value = this.target().attr(i)// + s.attrs[i].value
+ }
+ }
+
+ for(i in s.styles){
+ s.styles[i].value = this.target().style(i)
+ }
+
+ s.transformations = this.target().matrixify()
+
+ s.init = true
+ return this
+ }
+ , clearQueue: function(){
+ this.situations = []
+ return this
+ }
+ , clearCurrent: function(){
+ this.current = null
+ return this
+ }
+ /** stops the animation immediately
+ * @param jumpToEnd A Boolean indicating whether to complete the current animation immediately.
+ * @param clearQueue A Boolean indicating whether to remove queued animation as well.
+ * @return this
+ */
+ , stop: function(jumpToEnd, clearQueue){
+ if(!this.active) this.start()
+
+ if(clearQueue){
+ this.clearQueue()
+ }
+
+ this.active = false
+
+ if(jumpToEnd){
+ this.seek(1)
+ }
+
+ this.stopAnimFrame()
+ clearTimeout(this.timeout)
+
+ return this.clearCurrent()
+ }
+
+ /** resets the element to the state where the current element has started
+ * @return this
+ */
+ , reset: function(){
+ if(this.current){
+ var temp = this.current
+ this.stop()
+ this.current = temp
+ this.seek(0)
+ }
+ return this
+ }
+
+ // Stop the currently-running animation, remove all queued animations, and complete all animations for the element.
+ , finish: function(){
+
+ this.stop(true, false)
+
+ while(this.dequeue().current && this.stop(true, false));
+
+ return this.clearQueue().clearCurrent()
+ }
+
+ // set the internal animation pointer to the specified position and updates the visualisation
+ , seek: function(pos){
+ this.pos = pos
+ this.current.start = +new Date - pos * this.current.duration
+ this.current.end = this.current.start + this.current.duration
+ return this.step(true)
+ }
+
+ // speeds up the animation by the given factor
+ // this changes the duration of the animation
+ , speed: function(speed){
+ this.current.duration = this.current.duration * this.pos + (1-this.pos) * this.current.duration / speed
+ this.current.end = this.current.start + this.current.duration
+ return this.seek(this.pos)
+ }
+ // Make loopable
+ , loop: function(times, reverse) {
+ // store current loop and total loops
+ this.current.loop = times || true
+
+ if(reverse) return this.reverse()
+ return this
+ }
+
+ // pauses the animation
+ , pause: function(){
+ this.paused = true
+ this.stopAnimFrame()
+ clearTimeout(this.timeout)
+ return this
+ }
+
+ // unpause the animation
+ , play: function(){
+ if(!this.paused) return this
+ this.paused = false
+ return this.seek(this.pos)
+ }
+
+ /** toggle or set the direction of the animation
+ * true sets direction to backwards while false sets it to forwards
+ * @param reversed Boolean indicating whether to reverse the animation or not (default: toggle the reverse status)
+ * @return this
+ */
+ , reverse: function(reversed){
+ var c = this.last()
+
+ if(typeof reversed == 'undefined') c.reversed = !c.reversed
+ else c.reversed = reversed
+
+ return this
+ }
+
+
+ /**
+ * returns a float from 0-1 indicating the progress of the current animation
+ * @param eased Boolean indicating whether the returned position should be eased or not
+ * @return number
+ */
+ , progress: function(easeIt){
+ return easeIt ? this.current.easing(this.pos) : this.pos
+ }
+
+ /**
+ * adds a callback function which is called when the current animation is finished
+ * @param fn Function which should be executed as callback
+ * @return number
+ */
+ , after: function(fn){
+ var c = this.last()
+ , wrapper = function wrapper(e){
+ if(e.detail.situation == c){
+ fn.call(this, c)
+ this.off('finished.fx', wrapper) // prevent memory leak
+ }
+ }
+
+ this.target().on('finished.fx', wrapper)
+ return this
+ }
+
+ // adds a callback which is called whenever one animation step is performed
+ , during: function(fn){
+ var c = this.last()
+ , wrapper = function(e){
+ if(e.detail.situation == c){
+ fn.call(this, e.detail.pos, e.detail.eased, c)
+ }
+ }
+
+ // see above
+ this.target().off('during.fx', wrapper).on('during.fx', wrapper)
+
+ return this.after(function(){
+ this.off('during.fx', wrapper)
+ })
+ }
+
+ // calls after ALL animations in the queue are finished
+ , afterAll: function(fn){
+ var wrapper = function wrapper(e){
+ fn.call(this)
+ this.off('allfinished.fx', wrapper)
+ }
+
+ // see above
+ this.target().off('allfinished.fx', wrapper).on('allfinished.fx', wrapper)
+ return this
+ }
+
+ // calls on every animation step for all animations
+ , duringAll: function(fn){
+ var wrapper = function(e){
+ fn.call(this, e.detail.pos, e.detail.eased, e.detail.fx, e.detail.situation)
+ }
+
+ this.target().off('during.fx', wrapper).on('during.fx', wrapper)
+
+ return this.afterAll(function(){
+ this.off('during.fx', wrapper)
+ })
+ }
+
+ /**
+ * returns a float from 0-1 indicating the progress of the whole animation queue
+ * we recalculate the end time because it may be changed from methods like seek()
+ * @return number
+ */
+ // FIXME: current start always changes so the progress get a reset whenever one situation finishes. We need a global start which is only modified on pause and stop
+ , totalProgress: function(){
+ var start = this.current.start
+ , end = this.current
+
+ for(var i = 0, len = this.situations.length; i < len; ++i){
+ end += (situations[i].duration || 0) + (situations[i].delay || 0)
+ }
+
+ return (this.pos * this.current.duration + this.start - start) / (end - start)
+ }
+
+ , last: function(){
+ return this.situations.length ? this.situations[this.situations.length-1] : this.current
+ }
+
+ // adds one property to the animations
+ , add: function(method, args, type){
+ //if(this.situations.length){
+ // this.situations[this.situations.length-1][type || 'animations'][method] = args
+ //}else{
+ // this.current[type || 'animations'][method] = args
+ //}
+ this.last()[type || 'animations'][method] = args
+ setTimeout(function(){this.start()}.bind(this), 0)
+ return this
+ }
+
+ /** perform one step of the animation
+ * @param ignoreTime Boolean indicating whether to ignore time and use position directly or recalculate position based on time
+ * @return this
+ */
+ , step: function(ignoreTime){
+
+ // convert current time to position
+ if(!ignoreTime) this.pos = this.timeToPos(+new Date)
+
+ if(this.pos >= 1 && (this.current.loop === true || (typeof this.current.loop == 'number' && --this.current.loop))){
+ return this.seek(this.pos-1)
+ }
+
+ if(this.current.reversed) this.pos = 1 - this.pos
+
+ // correct position
+ if(this.pos > 1)this.pos = 1
+ if(this.pos < 0)this.pos = 0
+
+ // apply easing
+ var eased = this.current.easing(this.pos)
+
+ // call once-callbacks
+ for(var i in this.current.once){
+ if(i > this.lastPos && i <= eased){
+ this.current.once[i].call(this.target(), this.pos, eased)
+ delete this.current.once[i]
+ }
+ }
+
+ // fire during callback with position, eased position and current situation as parameter
+ this.target().fire('during', {pos: this.pos, eased: eased, fx: this, situation: this.current})
+
+ // apply the actual animation to every property
+ this.eachAt()
+
+ // do final code when situation is finished
+ if((this.pos == 1 && !this.current.reversed) || (this.current.reversed && this.pos == 0)){
+
+ // stop animation callback
+ cancelAnimationFrame(this.animationFrame)
+
+ // fire finished callback with current situation as parameter
+ this.target().fire('finished', {fx:this, situation: this.current})
+
+ if(!this.situations.length && !this.current && this.active){
+ this.target().fire('allfinished')
+ this.target().off('.fx')
+ this.active = false
+ }
+
+ // start next animation
+ if(this.active) this.dequeue()
+ else this.clearCurrent()
+
+ }else if(!this.paused && this.active){
+ // we continue animating when we are not at the end
+ this.startAnimFrame()
+ }
+
+ // save last eased position for once callback triggering
+ this.lastPos = eased
+ return this
+
+ }
+
+ // calculates the step for every property and calls block with it
+ // todo: include block directly cause it is used only for this purpose
+ , eachAt: function(){
+ var i, at, self = this, target = this.target(), c = this.current
+
+ // apply animations which can be called trough a method
+ for(i in c.animations){
+
+ at = [].concat(c.animations[i]).map(function(el){
+ return el.at ? el.at(c.easing(self.pos), self.pos) : el
+ })
+
+ target[i].apply(target, at)
+
+ }
+
+ // apply animation which has to be applied with attr()
+ for(i in c.attrs){
+
+ at = [i].concat(c.attrs[i]).map(function(el){
+ return el.at ? el.at(c.easing(self.pos), self.pos) : el
+ })
+
+ target.attr.apply(target, at)
+
+ }
+
+ // apply animation which has to be applied with style()
+ for(i in c.styles){
+
+ at = [i].concat(c.styles[i]).map(function(el){
+ return el.at ? el.at(c.easing(self.pos), self.pos) : el
+ })
+
+ target.style.apply(target, at)
+
+ }
+
+ // animate transformations which has to be chained
+ if(c.transforms.length){
+
+ // get inital transformations
+ at = c.transformations
+ for(i in c.transforms){
+
+ // get next transformation in chain
+ var a = c.transforms[i]
+
+ // multiply matrix directly
+ if(a instanceof SVG.Matrix){
+
+ if(a.relative){
+ at = at.multiply(a.at(this.pos))
+ }else{
+ at = at.morph(a).at(c.easing(this.pos))
+ }
+ continue
+ }
+
+ // when transformation is absolute we have to reset the needed transformation first
+ if(!a.relative)
+ a.undo(at.extract())
+
+ // and reapply it after
+ at = at.multiply(a.at(c.easing(this.pos)))
+ continue;
+
+ }
+
+ // set new matrix on element
+ target.matrix(at)
+ }
+
+ return this
+
+ }
+
+
+ // adds an once-callback which is called at a specific position and never again
+ , once: function(pos, fn, isEased){
+
+ if(!isEased)pos = this.current.easing(pos)
+
+ this.current.once[pos] = fn
+
+ return this
+ }
+
+ }
+
+, parent: SVG.Element
+
+ // Add method to parent elements
+, construct: {
+ // Get fx module or create a new one, then animate with given duration and ease
+ animate: function(o, easing, delay) {
+ return (this.fx || (this.fx = new SVG.FX(this))).animate(o, easing, delay)
+ }
+ , delay: function(delay){
+ return (this.fx || (this.fx = new SVG.FX(this))).delay(delay)
+ }
+ , stop: function(jumpToEnd, clearQueue) {
+ if (this.fx)
+ this.fx.stop(jumpToEnd, clearQueue)
+
+ return this
+ }
+ // Pause current animation
+ , pause: function() {
+ if (this.fx)
+ this.fx.pause()
+
+ return this
+ }
+ // Play paused current animation
+ , play: function() {
+ if (this.fx)
+ this.fx.play()
+
+ return this
+ }
+ }
+
+})
+
+// MorphObj is used whenever no morphable object is given
+SVG.MorphObj = SVG.invent({
+
+ create: function(to){
+ // prepare color for morphing
+ if(SVG.Color.isColor(to)) return new SVG.Color().morph(to)
+ // prepare number for morphing
+ if(SVG.regex.numberAndUnit.test(to)) return new SVG.Number().morph(to)
+
+ // prepare for plain morphing
+ this.value = 0
+ this.destination = to
+ }
+
+, extend: {
+ at: function(pos, real){
+ return real < 1 ? this.value : this.destination
+ },
+
+ valueOf: function(){
+ return this.value
+ }
+ }
+
+})
+
+SVG.extend(SVG.FX, {
+ // Add animatable attributes
+ attr: function(a, v, relative) {
+ // apply attributes individually
+ if (typeof a == 'object') {
+ for (var key in a)
+ this.attr(key, a[key])
+
+ } else {
+ // the MorphObj takes care about the right function used
+ this.add(a, new SVG.MorphObj(v), 'attrs')
+ }
+
+ return this
+ }
+ // Add animatable styles
+, style: function(s, v) {
+ if (typeof s == 'object')
+ for (var key in s)
+ this.style(key, s[key])
+
+ else
+ this.add(s, new SVG.MorphObj(v), 'styles')
+
+ return this
+ }
+ // Animatable x-axis
+, x: function(x, relative) {
+ if(this.target() instanceof SVG.G){
+ this.transform({x:x}, relative)
+ return this
+ }
+
+ var num = new SVG.Number().morph(x)
+ num.relative = relative
+ return this.add('x', num)
+ }
+ // Animatable y-axis
+, y: function(y, relative) {
+ if(this.target() instanceof SVG.G){
+ this.transform({y:y}, relative)
+ return this
+ }
+
+ var num = new SVG.Number().morph(y)
+ num.relative = relative
+ return this.add('y', num)
+ }
+ // Animatable center x-axis
+, cx: function(x) {
+ return this.add('cx', new SVG.Number().morph(x))
+ }
+ // Animatable center y-axis
+, cy: function(y) {
+ return this.add('cy', new SVG.Number().morph(y))
+ }
+ // Add animatable move
+, move: function(x, y) {
+ return this.x(x).y(y)
+ }
+ // Add animatable center
+, center: function(x, y) {
+ return this.cx(x).cy(y)
+ }
+ // Add animatable size
+, size: function(width, height) {
+ if (this.target() instanceof SVG.Text) {
+ // animate font size for Text elements
+ this.attr('font-size', width)
+
+ } else {
+ // animate bbox based size for all other elements
+ var box
+
+ if(!width || !height){
+ box = this.target().bbox()
+ }
+
+ if(!width){
+ width = box.width / box.height * height
+ }
+
+ if(!height){
+ height = box.height / box.width * width
+ }
+
+ this.add('width' , new SVG.Number().morph(width))
+ .add('height', new SVG.Number().morph(height))
+
+ }
+
+ return this
+ }
+ // Add animatable plot
+, plot: function(p) {
+ return this.add('plot', this.target().array().morph(p))
+ }
+ // Add leading method
+, leading: function(value) {
+ return this.target().leading ?
+ this.add('leading', new SVG.Number().morph(value)) :
+ this
+ }
+ // Add animatable viewbox
+, viewbox: function(x, y, width, height) {
+ if (this.target() instanceof SVG.Container) {
+ this.add('viewbox', new SVG.ViewBox(x, y, width, height))
+ }
+
+ return this
+ }
+, update: function(o) {
+ if (this.target() instanceof SVG.Stop) {
+ if (typeof o == 'number' || o instanceof SVG.Number) {
+ return this.update({
+ offset: arguments[0]
+ , color: arguments[1]
+ , opacity: arguments[2]
+ })
+ }
+
+ if (o.opacity != null) this.attr('stop-opacity', o.opacity)
+ if (o.color != null) this.attr('stop-color', o.color)
+ if (o.offset != null) this.attr('offset', o.offset)
+ }
+
+ return this
+ }
+}) \ No newline at end of file
diff --git a/src/group.js b/src/group.js
index 05d144c..9ec89f2 100644
--- a/src/group.js
+++ b/src/group.js
@@ -17,11 +17,11 @@ SVG.G = SVG.invent({
}
// Move by center over x-axis
, cx: function(x) {
- return x == null ? this.tbox().cx : this.x(x - this.tbox().width / 2)
+ return x == null ? this.gbox().cx : this.x(x - this.gbox().width / 2)
}
// Move by center over y-axis
, cy: function(y) {
- return y == null ? this.tbox().cy : this.y(y - this.tbox().height / 2)
+ return y == null ? this.gbox().cy : this.y(y - this.gbox().height / 2)
}
, gbox: function() {
diff --git a/src/helpers.js b/src/helpers.js
index ee86dc7..4de813f 100644
--- a/src/helpers.js
+++ b/src/helpers.js
@@ -1,3 +1,7 @@
+function is(el, obj){
+ return el instanceof obj
+}
+
// tests if a given selector matches an element
function matches(el, selector) {
return (el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector).call(el, selector);
diff --git a/src/matrix.js b/src/matrix.js
index 9b3c28e..e727b2f 100644
--- a/src/matrix.js
+++ b/src/matrix.js
@@ -14,7 +14,7 @@ SVG.Matrix = SVG.invent({
source : base
// merge source
- for (i = abcdef.length - 1; i >= 0; i--)
+ for (i = abcdef.length - 1; i >= 0; --i)
this[abcdef[i]] = source && typeof source[abcdef[i]] === 'number' ?
source[abcdef[i]] : base[abcdef[i]]
}
@@ -32,6 +32,8 @@ SVG.Matrix = SVG.invent({
// translation
x: this.e
, y: this.f
+ , transformedX:(this.e * Math.cos(skewX * Math.PI / 180) + this.f * Math.sin(skewX * Math.PI / 180)) / Math.sqrt(this.a * this.a + this.b * this.b)
+ , transformedY:(this.f * Math.cos(skewX * Math.PI / 180) + this.e * Math.sin(-skewX * Math.PI / 180)) / Math.sqrt(this.c * this.c + this.d * this.d)
// skew
, skewX: -skewX
, skewY: 180 / Math.PI * Math.atan2(py.y, py.x)
@@ -46,6 +48,7 @@ SVG.Matrix = SVG.invent({
, d: this.d
, e: this.e
, f: this.f
+ , matrix: new SVG.Matrix(this)
}
}
// Clone matrix
diff --git a/src/transform.js b/src/transform.js
index 2b6e9df..e6ad9ae 100644
--- a/src/transform.js
+++ b/src/transform.js
@@ -1,8 +1,8 @@
-SVG.extend(SVG.Element, SVG.FX, {
+SVG.extend(SVG.Element, {
// Add transformations
transform: function(o, relative) {
// get target in case of the fx module, otherwise reference this
- var target = this.target || this
+ var target = this
, matrix
// act as a getter
@@ -10,20 +10,11 @@ SVG.extend(SVG.Element, SVG.FX, {
// get current matrix
matrix = new SVG.Matrix(target).extract()
- // add parametric rotation
- if (typeof this.param === 'object') {
- matrix.rotation = this.param.rotation
- matrix.cx = this.param.cx
- matrix.cy = this.param.cy
- }
-
return typeof o === 'string' ? matrix[o] : matrix
}
// get current matrix
- matrix = /*this instanceof SVG.FX && this.attrs.transform ?
- this.attrs.transform :*/
- new SVG.Matrix(target)
+ matrix = new SVG.Matrix(target)
// ensure relative flag
relative = !!relative || !!o.relative
@@ -41,26 +32,12 @@ SVG.extend(SVG.Element, SVG.FX, {
// ensure centre point
ensureCentre(o, target)
- // relativize rotation value
- /*if (relative) {
- o.rotation += this.param && this.param.rotation != null ?
- this.param.rotation :
- matrix.extract().rotation
- }*/
-
- // store parametric values
- //this.param = o
-
- if(this instanceof SVG.FX) return this.attr('transform', o)
-
// apply transformation
- //if (this instanceof SVG.Element) {
- matrix = relative ?
- // relative
- matrix.rotate(o.rotation, o.cx, o.cy) :
- // absolute
- matrix.rotate(o.rotation - matrix.extract().rotation, o.cx, o.cy)
- //}
+ matrix = relative ?
+ // relative
+ matrix.rotate(o.rotation, o.cx, o.cy) :
+ // absolute
+ matrix.rotate(o.rotation - matrix.extract().rotation, o.cx, o.cy)
// act on scale
} else if (o.scale != null || o.scaleX != null || o.scaleY != null) {
@@ -116,7 +93,84 @@ SVG.extend(SVG.Element, SVG.FX, {
}
}
- return this.attr(this instanceof SVG.Pattern ? 'patternTransform' : this instanceof SVG.Gradient ? 'gradientTransform' : 'transform', matrix)
+ return this.attr('transform', matrix)
+ }
+})
+
+SVG.extend(SVG.FX, {
+ transform: function(o, relative) {
+ // get target in case of the fx module, otherwise reference this
+ var target = this.target()
+ , matrix
+
+ // act as a getter
+ if (typeof o !== 'object') {
+ // get current matrix
+ matrix = new SVG.Matrix(target).extract()
+
+ return typeof o === 'string' ? matrix[o] : matrix
+ }
+
+ // ensure relative flag
+ relative = !!relative || !!o.relative
+
+ // act on matrix
+ if (o.a != null) {
+ matrix = new SVG.Matrix(o)
+
+ // act on rotation
+ } else if (o.rotation != null) {
+ // ensure centre point
+ ensureCentre(o, target)
+
+ // apply transformation
+ matrix = new SVG.Rotate(o.rotation, o.cx, o.cy)
+
+ // act on scale
+ } else if (o.scale != null || o.scaleX != null || o.scaleY != null) {
+ // ensure centre point
+ ensureCentre(o, target)
+
+ // ensure scale values on both axes
+ o.scaleX = o.scale != null ? o.scale : o.scaleX != null ? o.scaleX : 1
+ o.scaleY = o.scale != null ? o.scale : o.scaleY != null ? o.scaleY : 1
+
+ matrix = new SVG.Scale(o.scaleX, o.scaleY, o.cx, o.cy)
+
+ // act on skew
+ } else if (o.skewX != null || o.skewY != null) {
+ // ensure centre point
+ ensureCentre(o, target)
+
+ // ensure skew values on both axes
+ o.skewX = o.skewX != null ? o.skewX : 0
+ o.skewY = o.skewY != null ? o.skewY : 0
+
+ matrix = new SVG.Skew(o.skewX, o.skewY, o.cx, o.cy)
+
+ // act on flip
+ } else if (o.flip) {
+ matrix = new SVG.Matrix().morph(new SVG.Matrix().flip(
+ o.flip
+ , o.offset == null ? target.bbox()['c' + o.flip] : o.offset
+ ))
+
+ // act on translate
+ } else if (o.x != null || o.y != null) {
+ matrix = new SVG.Translate(o.x, o.y)
+ }
+
+ if(!matrix) return this
+
+ matrix.relative = relative
+
+ var situation = this.situations.length ? this.situations[this.situations.length-1] : this.current
+
+ situation.transforms.push(matrix)
+
+ setTimeout(function(){this.start()}.bind(this), 0)
+
+ return this
}
})
@@ -163,3 +217,133 @@ SVG.extend(SVG.Element, {
}
})
+
+SVG.Transformation = SVG.invent({
+
+ create: function(source, inversed){
+
+ if(arguments.length > 1 && typeof inversed != 'boolean'){
+ return this.create([].slice.call(arguments))
+ }
+
+ if(typeof source == 'object'){
+ for(var i = 0, len = this.arguments.length; i < len; ++i){
+ this[this.arguments[i]] = source[this.arguments[i]]
+ }
+ }
+
+ if(Array.isArray(source)){
+ for(var i = 0, len = this.arguments.length; i < len; ++i){
+ this[this.arguments[i]] = source[i]
+ }
+ }
+
+ this.inversed = false
+
+ if(inversed === true){
+ this.inversed = true
+ }
+
+ }
+
+, extend: {
+
+ at: function(pos){
+
+ var params = []
+
+ for(var i = 0, len = this.arguments.length; i < len; ++i){
+ params.push(this[this.arguments[i]])
+ }
+
+ var m = this._undo || new SVG.Matrix()
+
+ m = new SVG.Matrix().morph(SVG.Matrix.prototype[this.method].apply(m, params)).at(pos)
+
+ return this.inversed ? m.inverse() : m
+
+ }
+
+ , undo: function(o){
+ this._undo = new SVG[capitalize(this.method)](o, true).at(1)
+ return this
+ }
+
+ }
+
+})
+
+SVG.Translate = SVG.invent({
+
+ parent: SVG.Matrix
+, inherit: SVG.Transformation
+
+, create: function(source, inversed){
+ if(typeof source == 'object') this.constructor.call(this, source, inversed)
+ else this.constructor.call(this, [].slice.call(arguments))
+ }
+
+, extend: {
+ arguments: ['transformedX', 'transformedY']
+ , method: 'translate'
+ }
+
+})
+
+SVG.Rotate = SVG.invent({
+
+ parent: SVG.Matrix
+, inherit: SVG.Transformation
+
+, create: function(source, inversed){
+ if(typeof source == 'object') this.constructor.call(this, source, inversed)
+ else this.constructor.call(this, [].slice.call(arguments))
+ }
+
+, extend: {
+ arguments: ['rotation', 'cx', 'cy']
+ , method: 'rotate'
+ , at: function(pos){
+ var m = new SVG.Matrix().rotate(new SVG.Number().morph(this.rotation - (this._undo ? this._undo.rotation : 0)).at(pos), this.cx, this.cy)
+ return this.inversed ? m.inverse() : m
+ }
+ , undo: function(o){
+ this._undo = o
+ }
+ }
+
+})
+
+SVG.Scale = SVG.invent({
+
+ parent: SVG.Matrix
+, inherit: SVG.Transformation
+
+, create: function(source, inversed){
+ if(typeof source == 'object') this.constructor.call(this, source, inversed)
+ else this.constructor.call(this, [].slice.call(arguments))
+ }
+
+, extend: {
+ arguments: ['scaleX', 'scaleY', 'cx', 'cy']
+ , method: 'scale'
+ }
+
+})
+
+SVG.Skew = SVG.invent({
+
+ parent: SVG.Matrix
+, inherit: SVG.Transformation
+
+, create: function(source, inversed){
+ if(typeof source == 'object') this.constructor.call(this, source, inversed)
+ else this.constructor.call(this, [].slice.call(arguments))
+ }
+
+, extend: {
+ arguments: ['skewX', 'skewY', 'cx', 'cy']
+ , method: 'skew'
+ }
+
+})
diff --git a/src/viewbox.js b/src/viewbox.js
index ce9c902..bb106b0 100644
--- a/src/viewbox.js
+++ b/src/viewbox.js
@@ -1,63 +1,115 @@
-SVG.ViewBox = function(element) {
- var x, y, width, height
- , wm = 1 // width multiplier
- , hm = 1 // height multiplier
- , box = element.bbox()
- , view = (element.attr('viewBox') || '').match(/-?[\d\.]+/g)
- , we = element
- , he = element
-
- // get dimensions of current node
- width = new SVG.Number(element.width())
- height = new SVG.Number(element.height())
-
- // find nearest non-percentual dimensions
- while (width.unit == '%') {
- wm *= width.value
- width = new SVG.Number(we instanceof SVG.Doc ? we.parent().offsetWidth : we.parent().width())
- we = we.parent()
- }
- while (height.unit == '%') {
- hm *= height.value
- height = new SVG.Number(he instanceof SVG.Doc ? he.parent().offsetHeight : he.parent().height())
- he = he.parent()
- }
-
- // ensure defaults
- this.x = box.x
- this.y = box.y
- this.width = width * wm
- this.height = height * hm
- this.zoom = 1
-
- if (view) {
- // get width and height from viewbox
- x = parseFloat(view[0])
- y = parseFloat(view[1])
- width = parseFloat(view[2])
- height = parseFloat(view[3])
-
- // calculate zoom accoring to viewbox
- this.zoom = ((this.width / this.height) > (width / height)) ?
- this.height / height :
- this.width / width
-
- // calculate real pixel dimensions on parent SVG.Doc element
- this.x = x
- this.y = y
- this.width = width
- this.height = height
-
+SVG.ViewBox = SVG.invent({
+
+ create: function(source) {
+ var i, base = [1, 0, 0, 1]
+
+ var x, y, width, height, box, view, we, he
+ , wm = 1 // width multiplier
+ , hm = 1 // height multiplier
+ , reg = /-?[\d\.]+/g
+
+ if(source instanceof SVG.Element){
+
+ we = source
+ he = source
+ view = (source.attr('viewBox') || '').match(reg)
+ box = source.bbox
+
+ // get dimensions of current node
+ width = new SVG.Number(source.width())
+ height = new SVG.Number(source.height())
+
+ // find nearest non-percentual dimensions
+ while (width.unit == '%') {
+ wm *= width.value
+ width = new SVG.Number(we instanceof SVG.Doc ? we.parent().offsetWidth : we.parent().width())
+ we = we.parent()
+ }
+ while (height.unit == '%') {
+ hm *= height.value
+ height = new SVG.Number(he instanceof SVG.Doc ? he.parent().offsetHeight : he.parent().height())
+ he = he.parent()
+ }
+
+ // ensure defaults
+ this.x = 0
+ this.y = 0
+ this.width = width * wm
+ this.height = height * hm
+ this.zoom = 1
+
+ if (view) {
+ // get width and height from viewbox
+ x = parseFloat(view[0])
+ y = parseFloat(view[1])
+ width = parseFloat(view[2])
+ height = parseFloat(view[3])
+
+ // calculate zoom accoring to viewbox
+ this.zoom = ((this.width / this.height) > (width / height)) ?
+ this.height / height :
+ this.width / width
+
+ // calculate real pixel dimensions on parent SVG.Doc element
+ this.x = x
+ this.y = y
+ this.width = width
+ this.height = height
+
+ }
+
+ }else{
+ // ensure source as object
+ source = typeof source === 'string' ?
+ source.match(reg).map(function(el){ return parseFloat(el) }) :
+ Array.isArray(source) ?
+ source :
+ typeof source == 'object' ?
+ [source.x, source.y, source.width, source.height] :
+ arguments.length == 4 ?
+ [].slice.call(arguments) :
+ base
+
+ this.x = source[0]
+ this.y = source[1]
+ this.width = source[2]
+ this.height = source[3]
+ }
+
+
}
-
-}
-
-//
-SVG.extend(SVG.ViewBox, {
- // Parse viewbox to string
- toString: function() {
- return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height
+
+, extend: {
+
+ toString: function() {
+ return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height
+ }
+ , morph: function(v){
+
+ var v = arguments.length == 1 ?
+ [v.x, v.y, v.width, v.height] :
+ [].slice.call(arguments)
+
+ this.destination = new SVG.ViewBox(v)
+
+ return this
+
+ }
+
+ , at: function(pos) {
+
+ if(!this.destination) return this
+
+ return new SVG.ViewBox([
+ this.x + (this.destination.x - this.x) * pos
+ , this.y + (this.destination.y - this.y) * pos
+ , this.width + (this.destination.width - this.width) * pos
+ , this.height + (this.destination.height - this.height) * pos
+ ])
+
+ }
+
}
-
+
}) \ No newline at end of file