SVG.FX = function(element) { /* store target element */ this.target = element } // SVG.extend(SVG.FX, { // Add animation parameters and start animation animate: function(d, ease, delay) { var akeys, tkeys, skeys, key , element = this.target , fx = this /* dissect object if one is passed */ if (typeof d == 'object') { delay = d.delay ease = d.ease d = d.duration } /* ensure default duration and easing */ d = d == null ? 1000 : d ease = ease || '<>' /* process values */ fx.to = function(pos) { var i /* normalise pos */ pos = pos < 0 ? 0 : pos > 1 ? 1 : pos /* collect attribute keys */ if (akeys == null) { akeys = [] for (key in fx.attrs) akeys.push(key) /* make sure morphable elements are scaled, translated and morphed all together */ if (element.morphArray) { /* get destination */ var box , p = new element.morphArray(fx._plot || element.points.toString()) /* add size */ if (fx._size) p.size(fx._size.width.to, fx._size.height.to) /* add movement */ box = p.bbox() if (fx._x) p.move(fx._x.to, box.y) else if (fx._cx) p.move(fx._cx.to - box.width / 2, box.y) box = p.bbox() if (fx._y) p.move(box.x, fx._y.to) else if (fx._cy) p.move(box.x, fx._cy.to - box.height / 2) /* delete element oriented changes */ delete fx._x delete fx._y delete fx._cx delete fx._cy delete fx._size fx._plot = element.points.morph(p) } } /* collect transformation keys */ if (tkeys == null) { tkeys = [] for (key in fx.trans) tkeys.push(key) } /* collect style keys */ if (skeys == null) { skeys = [] for (key in fx.styles) skeys.push(key) } /* apply easing */ pos = ease == '<>' ? (-Math.cos(pos * Math.PI) / 2) + 0.5 : ease == '>' ? Math.sin(pos * Math.PI / 2) : ease == '<' ? -Math.cos(pos * Math.PI / 2) + 1 : ease == '-' ? pos : typeof ease == 'function' ? ease(pos) : pos /* run all x-position properties */ if (fx._x) element.x(fx._at(fx._x, pos)) else if (fx._cx) element.cx(fx._at(fx._cx, pos)) /* run all y-position properties */ if (fx._y) element.y(fx._at(fx._y, pos)) else if (fx._cy) element.cy(fx._at(fx._cy, pos)) /* run all size properties */ if (fx._size) element.size(fx._at(fx._size.width, pos), fx._at(fx._size.height, pos)) /* run plot function */ if (fx._plot) element.plot(fx._plot.at(pos)) /* run all viewbox properties */ if (fx._viewbox) element.viewbox( fx._at(fx._viewbox.x, pos) , fx._at(fx._viewbox.y, pos) , fx._at(fx._viewbox.width, pos) , fx._at(fx._viewbox.height, pos) ) /* animate attributes */ for (i = akeys.length - 1; i >= 0; i--) element.attr(akeys[i], fx._at(fx.attrs[akeys[i]], pos)) /* animate transformations */ for (i = tkeys.length - 1; i >= 0; i--) element.transform(tkeys[i], fx._at(fx.trans[tkeys[i]], pos)) /* animate styles */ for (i = skeys.length - 1; i >= 0; i--) element.style(skeys[i], fx._at(fx.styles[skeys[i]], pos)) /* callback for each keyframe */ if (fx._during) fx._during.call(element, pos, function(from, to) { return fx._at({ from: from, to: to }, pos) }) } if (typeof d === 'number') { /* delay animation */ this.timeout = setTimeout(function() { var start = new Date().getTime() /* initialize situation object */ fx.situation = { interval: 1000 / 60 , start: start , play: true , finish: start + d , duration: d } /* start animation */ fx.interval = setInterval(function(){ if (fx.situation.play === true) { // This code was borrowed from the emile.js micro framework by Thomas Fuchs, aka MadRobby. var time = new Date().getTime() , pos = time > fx.situation.finish ? 1 : (time - fx.situation.start) / d /* process values */ fx.to(pos) /* finish off animation */ if (time > fx.situation.finish) { if (fx._plot) element.plot(new SVG.PointArray(fx._plot.destination).settle()) clearInterval(fx.interval) fx._after ? fx._after.apply(element, [fx]) : fx.stop() } } }, d > fx.situation.interval ? fx.situation.interval : d) }, delay || 0) } return this } // Get bounding box of target element , bbox: function() { return this.target.bbox() } // Add animatable attributes , attr: function(a, v, n) { if (typeof a == 'object') for (var key in a) this.attr(key, a[key]) else this.attrs[a] = { from: this.target.attr(a), to: v } return this } // Add animatable transformations , transform: function(o, v) { if (arguments.length == 1) { /* parse matrix string */ o = this.target._parseMatrix(o) /* dlete matrixstring from object */ delete o.matrix /* store matrix values */ for (v in o) this.trans[v] = { from: this.target.trans[v], to: o[v] } } else { /* apply transformations as object if key value arguments are given*/ var transform = {} transform[o] = v this.transform(transform) } 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.styles[s] = { from: this.target.style(s), to: v } return this } // Animatable x-axis , x: function(x) { this._x = { from: this.target.x(), to: x } return this } // Animatable y-axis , y: function(y) { this._y = { from: this.target.y(), to: y } return this } // Animatable center x-axis , cx: function(x) { this._cx = { from: this.target.cx(), to: x } return this } // Animatable center y-axis , cy: function(y) { this._cy = { from: this.target.cy(), to: y } return this } // 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 = this.target.bbox() this._size = { width: { from: box.width, to: width } , height: { from: box.height, to: height } } } return this } // Add animatable plot , plot: function(p) { this._plot = p return this } // Add animatable viewbox , viewbox: function(x, y, width, height) { if (this.target instanceof SVG.Container) { var box = this.target.viewbox() this._viewbox = { x: { from: box.x, to: x } , y: { from: box.y, to: y } , width: { from: box.width, to: width } , height: { from: box.height, to: height } } } return this } // Add animateable gradient update , 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 } // Add callback for each keyframe , during: function(during) { this._during = during return this } // Callback after animation , after: function(after) { this._after = after return this } // Stop running animation , stop: function() { /* stop current animation */ clearTimeout(this.timeout) clearInterval(this.interval) /* reset storage for properties that need animation */ this.attrs = {} this.trans = {} this.styles = {} this.situation = {} delete this._x delete this._y delete this._cx delete this._cy delete this._size delete this._plot delete this._after delete this._during delete this._viewbox return this } // Pause running animation , pause: function() { if (this.situation.play === true) { this.situation.play = false this.situation.pause = new Date().getTime() } return this } // Play running animation , play: function() { if (this.situation.play === false) { var pause = new Date().getTime() - this.situation.pause this.situation.finish += pause this.situation.start += pause this.situation.play = true } return this } // Private: calculate position according to from and to , _at: function(o, pos) { /* number recalculation */ return typeof o.from == 'number' ? o.from + (o.to - o.from) * pos : /* unit recalculation */ SVG.regex.unit.test(o.to) ? new SVG.Number(o.to) .minus(new SVG.Number(o.from)) .times(pos) .plus(new SVG.Number(o.from)) : /* color recalculation */ o.to && (o.to.r || SVG.Color.test(o.to)) ? this._color(o, pos) : /* for all other values wait until pos has reached 1 to return the final value */ pos < 1 ? o.from : o.to } // Private: tween color , _color: function(o, pos) { var from, to /* normalise pos */ pos = pos < 0 ? 0 : pos > 1 ? 1 : pos /* convert FROM */ from = new SVG.Color(o.from) /* convert TO hex to rgb */ to = new SVG.Color(o.to) /* tween color and return hex */ return new SVG.Color({ r: ~~(from.r + (to.r - from.r) * pos) , g: ~~(from.g + (to.g - from.g) * pos) , b: ~~(from.b + (to.b - from.b) * pos) }).toHex() } }) // SVG.extend(SVG.Element, { // Get fx module or create a new one, then animate with given duration and ease animate: function(d, ease, delay) { return (this.fx || (this.fx = new SVG.FX(this))).stop().animate(d, ease, delay) } // Stop current animation; this is an alias to the fx instance , stop: function() { if (this.fx) this.fx.stop() 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 } }) // Usage: // rect.animate(1500, '>').move(200, 300).after(function() { // this.fill({ color: '#f06' }) // })