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 && (fx._plot || akeys.indexOf('points') > -1)) { /* get destination */ var box , p = new element.morphArray(fx._plot || fx.attrs.points || element.array) /* 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.array.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 plot function */ if (fx._plot) { element.plot(fx._plot.at(pos)) } else { /* run all x-position properties */ if (fx._x) element.x(at(fx._x, pos)) else if (fx._cx) element.cx(at(fx._cx, pos)) /* run all y-position properties */ if (fx._y) element.y(at(fx._y, pos)) else if (fx._cy) element.cy(at(fx._cy, pos)) /* run all size properties */ if (fx._size) element.size(at(fx._size.width, pos), at(fx._size.height, pos)) } /* run all viewbox properties */ if (fx._viewbox) element.viewbox( at(fx._viewbox.x, pos) , at(fx._viewbox.y, pos) , at(fx._viewbox.width, pos) , at(fx._viewbox.height, pos) ) /* animate attributes */ for (i = akeys.length - 1; i >= 0; i--) element.attr(akeys[i], at(fx.attrs[akeys[i]], pos)) /* animate transformations */ for (i = tkeys.length - 1; i >= 0; i--) element.transform(tkeys[i], at(fx.trans[tkeys[i]], pos)) /* animate styles */ for (i = skeys.length - 1; i >= 0; i--) element.style(skeys[i], at(fx.styles[skeys[i]], pos)) /* callback for each keyframe */ if (fx._during) fx._during.call(element, pos, function(from, to) { return 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 } /* render function */ fx.render = 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()) if (fx._loop === true || (typeof fx._loop == 'number' && fx._loop > 1)) { if (typeof fx._loop == 'number') --fx._loop fx.animate(d, ease, delay) } else { fx._after ? fx._after.apply(element, [fx]) : fx.stop() } } else { requestAnimFrame(fx.render) } } else { requestAnimFrame(fx.render) } } /* start animation */ fx.render() }, delay || 0) } return this } // Get bounding box of target element , bbox: function() { return this.target.bbox() } // Add animatable attributes , attr: function(a, v) { if (typeof a == 'object') { for (var key in a) this.attr(key, a[key]) } else { var from = this.target.attr(a) this.attrs[a] = SVG.Color.isColor(from) ? new SVG.Color(from).morph(v) : SVG.regex.unit.test(from) ? new SVG.Number(from).morph(v) : { from: from, 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 } // Make loopable , loop: function(times) { this._loop = times || true 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._loop 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 } }) 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 } }) // Calculate position according to from and to function at(o, pos) { /* number recalculation (don't bother converting to SVG.Number for performance reasons) */ return typeof o.from == 'number' ? o.from + (o.to - o.from) * pos : /* instance recalculation */ o instanceof SVG.Color || o instanceof SVG.Number ? o.at(pos) : /* for all other values wait until pos has reached 1 to return the final value */ pos < 1 ? o.from : o.to } // Shim layer with setTimeout fallback by Paul Irish window.requestAnimFrame = (function(){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || function (c) { window.setTimeout(c, 1000 / 60) } })()