summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorwout <wout@impinc.co.uk>2013-01-03 21:15:32 +0100
committerwout <wout@impinc.co.uk>2013-01-03 21:15:32 +0100
commit6f49a9d4c5ada0b7335c08fbcef623cf32898938 (patch)
tree560c92b904ae232089874507bde65adaaf2f4d80 /src
parent0da8f1d9c9794155c29b8e8f9a0b5ca940061033 (diff)
downloadsvg.js-6f49a9d4c5ada0b7335c08fbcef623cf32898938.tar.gz
svg.js-6f49a9d4c5ada0b7335c08fbcef623cf32898938.zip
Added the SVG.FX module for animations
Diffstat (limited to 'src')
-rw-r--r--src/element.js8
-rw-r--r--src/fx.js249
-rw-r--r--src/sugar.js47
3 files changed, 296 insertions, 8 deletions
diff --git a/src/element.js b/src/element.js
index aaa6010..719c471 100644
--- a/src/element.js
+++ b/src/element.js
@@ -4,9 +4,13 @@ SVG.Element = function Element(n) {
this.node = n;
// initialize attribute store
- this.attrs = {};
+ this.attrs = {
+ 'fill-opacity': 1,
+ 'stroke-opacity': 1,
+ 'stroke-width': 0
+ };
- // initialize transformations store
+ // initialize transformation store
this.trans = {
x: 0,
y: 0,
diff --git a/src/fx.js b/src/fx.js
new file mode 100644
index 0000000..c35478c
--- /dev/null
+++ b/src/fx.js
@@ -0,0 +1,249 @@
+
+SVG.FX = function FX(e) {
+ // store target element
+ this.target = e;
+};
+
+// add FX methods
+SVG.extend(SVG.FX, {
+
+ // animation parameters and animate
+ animate: function(duration, ease) {
+ // ensure default duration adn easing
+ duration = duration || 1000;
+ ease = ease || '<>';
+
+ var akeys, tkeys, tvalues,
+ element = this.target,
+ fx = this,
+ start = (new Date).getTime(),
+ finish = start + duration;
+
+ // start animation
+ this.interval = setInterval(function(){
+ var i,
+ time = (new Date).getTime(),
+ pos = time > finish ? 1 : (time - start) / duration;
+
+ // collect attribute keys
+ if (akeys == null) {
+ akeys = [];
+ for (var k in fx.attrs)
+ akeys.push(k);
+ };
+
+ // collect transformation keys
+ if (tkeys == null) {
+ tkeys = [];
+ for (var k in fx.trans)
+ tkeys.push(k);
+
+ tvalues = {};
+ };
+
+ // 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 position properties
+ if (fx._move)
+ element.move(fx._at(fx._move.x, pos), fx._at(fx._move.y, pos));
+ else if (fx._center)
+ element.move(fx._at(fx._center.x, pos), fx._at(fx._center.y, pos));
+
+ // run all size properties
+ if (fx._size)
+ element.size(fx._at(fx._size.width, pos), fx._at(fx._size.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
+ if (tkeys.length > 0) {
+ for (i = tkeys.length - 1; i >= 0; i--)
+ tvalues[tkeys[i]] = fx._at(fx.trans[tkeys[i]], pos);
+
+ element.transform(tvalues);
+ }
+
+ // finish off animation
+ if (time > finish) {
+ clearInterval(fx.interval);
+ fx._after ? fx._after.apply(element) : fx.stop();
+ }
+
+ }, 10);
+
+ return this;
+ },
+
+ // animated attributes
+ attr: function(a, v, n) {
+ if (typeof a == 'object')
+ for (var k in a)
+ this.attr(k, a[k]);
+
+ else
+ this.attrs[a] = { from: this.target.attr(a), to: v };
+ },
+
+ // animated transformations
+ transform: function(o) {
+ for (var k in o)
+ this.trans[k] = { from: this.target.trans[k], to: o[k] };
+
+ return this;
+ },
+
+ // animated move
+ move: function(x, y) {
+ var b = this.target.bbox();
+
+ this._move = {
+ x: { from: b.x, to: x },
+ y: { from: b.y, to: y }
+ };
+
+ return this;
+ },
+
+ // animated size
+ size: function(w, h) {
+ var b = this.target.bbox();
+
+ this._size = {
+ width: { from: b.width, to: w },
+ height: { from: b.height, to: h }
+ };
+
+ return this;
+ },
+
+ // animated center
+ center: function(x, y) {
+ var b = this.target.bbox();
+
+ this._move = {
+ x: { from: b.cx, to: x },
+ y: { from: b.cy, to: y }
+ };
+
+ return this;
+ },
+
+ // stop animation
+ stop: function() {
+ // stop current animation
+ clearInterval(this.interval);
+
+ // create / reset storage for properties that need animation
+ this.attrs = {};
+ this.trans = {};
+ this._move = null;
+ this._size = null;
+ this._after = null;
+
+ return this;
+ },
+
+ // private: at position according to from and to
+ _at: function(o, p) {
+ // if a number, calculate pos
+ return typeof o.from == 'number' ?
+ o.from + (o.to - o.from) * p :
+
+ // if animating to a color
+ o.to.r || /^#/.test(o.to) ?
+ this._color(o, p) :
+
+ // for all other values wait until pos has reached 1 to return the final value
+ p < 1 ? o.from : o.to;
+ },
+
+ // private: tween color
+ _color: function(o, p) {
+ // convert hex to rgb and store it for further reference
+ if (typeof o.from !== 'object')
+ o.from = this._h2r(o.from || '#000');
+
+ // convert hex to rgb and store it for further reference
+ if (typeof o.to !== 'object')
+ o.to = this._h2r(o.to);
+
+ // tween color and return hex
+ return this._r2h({
+ r: ~~(o.from.r + (o.to.r - o.from.r) * p),
+ g: ~~(o.from.g + (o.to.g - o.from.g) * p),
+ b: ~~(o.from.b + (o.to.b - o.from.b) * p)
+ });
+ },
+
+ // private: convert hex to rgb object
+ _h2r: function(h) {
+ // parse full hex
+ var m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(this._fh(h));
+
+ // if the hex is successfully parsed, return it in rgb, otherwise return black
+ return m ? {
+ r: parseInt(m[1], 16),
+ g: parseInt(m[2], 16),
+ b: parseInt(m[3], 16)
+ } : { r: 0, g: 0, b: 0 };
+ },
+
+ // private: convert rgb object to hex string
+ _r2h: function(r) {
+ return '#' + (r.r + 256 * r.g + 65536 * r.b).toString(16);
+ },
+
+ // private: force potential 3-based hex to 6-based
+ _fh: function(h) {
+ return h.length == 4 ?
+ [ '#',
+ h.substring(1, 2), h.substring(1, 2),
+ h.substring(2, 3), h.substring(2, 3),
+ h.substring(3, 4), h.substring(3, 4)
+ ].join('') : h;
+ }
+
+});
+
+
+// delay: delay animation for a given amount of ms
+// after: callback for when animation has finished
+['delay', 'after'].forEach(function(m) {
+ SVG.FX.prototype[m] = function(v) {
+ this['_' + m] = v;
+
+ return this;
+ };
+});
+
+
+// make SVG.Element FX-aware
+SVG.extend(SVG.Element, {
+
+ // get fx module or create a new one, then animate with given ms and ease
+ animate: function(d, e) {
+ return (this._fx || (this._fx = new SVG.FX(this))).stop().animate(d, e);
+ },
+
+ // stop current animation
+ stop: function() {
+ this._fx.stop();
+
+ return this;
+ }
+
+});
+
diff --git a/src/sugar.js b/src/sugar.js
index 47ab35a..c6b5c75 100644
--- a/src/sugar.js
+++ b/src/sugar.js
@@ -45,13 +45,9 @@ SVG.extend(SVG.Shape, {
SVG.extend(SVG.Element, {
// rotation
- rotate: function(d, x, y) {
- var b = this.bbox();
-
+ rotate: function(d) {
return this.transform({
- rotation: d || 0,
- cx: x == null ? b.cx : x,
- cy: y == null ? b.cx : y
+ rotation: d || 0
});
},
@@ -96,5 +92,44 @@ SVG.extend(SVG.Text, {
});
+// add methods to SVG.FX
+if (SVG.FX) {
+ // add sugar for fill and stroke
+ ['fill', 'stroke'].forEach(function(m) {
+ SVG.FX.prototype[m] = function(o) {
+ var a, k;
+
+ for (k in o) {
+ a = k == 'color' ? m : m + '-' + k;
+ this.attrs[a] = {
+ from: this.target.attrs[a],
+ to: o[k]
+ };
+ };
+
+ return this;
+ };
+ });
+
+ SVG.extend(SVG.FX, {
+
+ // rotation
+ rotate: function(d) {
+ return this.transform({
+ rotation: d || 0
+ });
+ },
+
+ // skew
+ skew: function(x, y) {
+ return this.transform({
+ skewX: x || 0,
+ skewY: y || 0
+ });
+ }
+
+ });
+}
+