/** * Animation of SVG DOM Nodes * @class ludo.svg.Animation * @example * circle.animate({ * cx : 100, cy: 100, r: 10 * }, 200, * ludo.svg.easing.outCubic, * function(){ console.log('finished') } * ); */ ludo.svg.Animation = new Class({ animationRate: 13, color: undefined, _queue:undefined, _animationIds:undefined, testing:false, initialize:function(){ this._queue = {}; this._animationIds = {}; }, colorUtil: function () { if (this.color == undefined)this.color = new ludo.color.Color(); return this.color; }, getPathSegments:function(path){ if(path.substr == undefined)return path; path = path.replace(/,/g, " "); path = path.replace(/\s+/g, " "); var tokens = path.split(/\s/g); jQuery.each(tokens, function(index, value){ if(!isNaN(value)){ tokens[index] = parseFloat(value); } }); return tokens; }, animate:function(node, properties,options){ if(this.color == undefined)this.colorUtil(); this.queue({ node: node, properties:properties, options: options }); }, queue:function(animation){ animation.__finish = this.next.bind(this); animation.id = animation.node.id; animation.animationId = String.uniqueID(); this._animationIds[animation.id] = animation.animationId; var hasQueue = this.hasQueue(animation); var shouldQueue = animation.options.queue != undefined ? animation.options.queue : true; if(this._queue[animation.id] == undefined){ this._queue[animation.id] = []; } if(shouldQueue){ this._queue[animation.id].push(animation); } if(!shouldQueue || !hasQueue){ this.fn.call(this, animation); } }, hasQueue:function(animation){ return this._queue[animation.id] != undefined && this._queue[animation.id].length > 0; }, fn: function (animation) { var node = animation.node; var properties = animation.properties; var options = animation.options; var duration = options.duration || 400; var easing = options.easing || ludo.svg.easing.inSine; var changes = {}; var start = {}; var special = {}; var finishedFn = animation.__finish; jQuery.each(properties, function (key, value) { special[key] = true; switch (key) { case "d": var p = node.attr("d"); start[key] = this.getPathSegments(p); changes[key] = this.getPathSegments(value); jQuery.each(changes[key], function(index, value){ if(!isNaN(value)){ changes[key][index] = value - start[key][index]; } }); break; case 'fill': case 'stroke': case 'stop-color': var clr = node.attr(key) || '#000000'; if (clr.length == 4) clr = clr + clr.substr(1); var u = this.colorUtil(); var rgb = u.rgbColors(clr); var to = u.rgbColors(value); changes[key] = [ to.r - rgb.r, to.g - rgb.g, to.b - rgb.b ]; start[key] = rgb; break; case 'translate': var add = jQuery.type(value[0]) == 'string' && (/[+\-]/.test(value[0])); value[0] = parseInt(value[0]); value[1] = value[1] ? parseInt(value[1]) : 0; var cur = add ? [0,0] : node.getTranslate(); changes[key] = [ value[0] - cur[0], value[1] - cur[1] ]; start[key] = node.getTranslate(); special[key] = true; break; case 'rotate': var c = node.getRotate(); changes[key] = [ value[0] - c[0], value[1], value[2] ]; start[key] = c[0]; break; case 'scale': default: var current = parseFloat(node.get(key)); if(isNaN(current)){ current = 1; } changes[key] = value - current; start[key] = current; special[key] = false; } }.bind(this)); var progress = options.progress; var startFn = options.start; var stepFn = options.step; var fn = function (t, d) { if(options.validate != undefined){ var success = options.validate.call(this, animation.animationId, this._animationIds[animation.id]); if(!success){ finishedFn.call(ludo.svgAnimation , animation); return; } } var isFinished = t >= d; if (!isFinished && !this.testing) { fn.delay(this.animationRate, this, [t + 1, d]); } var vals; if(t == 0 && startFn){ startFn.call(node); } if(isFinished)t = d; var delta = easing(t, 0, 1, d); var x,y; for(var key in changes) { if(changes.hasOwnProperty(key)) { var value = changes[key]; if (special[key]) { switch (key) { case 'd': var v = []; jQuery.each(start[key], function (index, v2) { if (isNaN(v2)) { v.push(v2); } else { v.push(v2 + (value[index] * delta)) } }); if(stepFn != undefined)value = stepFn.call(this, v, delta, t/d) || value; node.set("d", v.join(" ")); break; case 'stroke': case 'fill': case 'stop-color': var r = Math.round(start[key].r + (delta * value[0])); var g = Math.round(start[key].g + (delta * value[1])); var b = Math.round(start[key].b + (delta * value[2])); var c = this.color.toRGB(r, g, b); node.el.setAttribute(key, c); node._attr[key] = c; node.dirty = true; break; case 'translate': x = start[key][0] + (delta * value[0]); y = start[key][1] + (delta * value[1]); node._getMatrix().setTranslate(x, y); break; case 'rotate': var rotate = start[key] + (delta * value[0]); x = value[1]; y = value[2]; rotate = rotate % 360; node._getMatrix().setRotation(rotate, x, y); break; } } else { var val = start[key] + (value * delta); if(stepFn != undefined)val = stepFn.call(this, val, delta, t/d) || val; node.el.setAttribute(key, val); node._attr[key] = val; node.dirty = true; if (progress) { if (vals == undefined) { vals = {}; } vals[key] = val; } } } } /* if (options.step != undefined) { options.step.call(node, node, vals, delta, t / d); } */ if(progress!= undefined){ progress.call(node, t/d, vals); } if (isFinished) { if (options.complete != undefined) { options.complete.call(node); } finishedFn.call(ludo.svgAnimation , animation); } if(this.testing && !isFinished){ fn.call(this, t + 1, d); } }.bind(this); animation.startTime = new Date().getTime(); fn.call(this, 1, Math.ceil(duration / this.animationRate)); }, next:function(anim){ var index = this._queue[anim.id].indexOf(anim); if(index >= 0){ this._queue[anim.id].splice(index,1); } if(this._queue[anim.id].length > 0){ this.fn(this._queue[anim.id][0]); } } }); ludo.svgAnimation = new ludo.svg.Animation(); /** * Easing methods for SVG animations. * * To see how the different functions work, see the <a href="../demo/svg/animation.php">SVG animation demo</a>. * * @class ludo.svg.easing * @example * circle.animate({ * cx : 100, cy: 100, r: 10 * }, { * duration: 200, * easing: ludo.svg.easing.outCubic, * complete: function(){ console.log('finished') } * }); */ ludo.svg.easing = { /** * * @function linear * @memberof ludo.svg.easing * @example * var v = new ludo.View({ * renderTo: document.body, * layout:{ * width:'matchParent', height:'matchParent' * } * }); * var svg = v.svg(); * * var circle = svg.$('circle', { cx: 100, cy: 100, r: 50 }); * circle.css('fill', '#ff0000'); * svg.append(circle); * circle.animate({ * cx:300, cy: 200 * },{ * easing: ludo.svg.easing.linear * duration: 1000, * complete:function(){ * console.log('completed'); * } * }); */ linear: function (t, b, c, d) { return c * t / d + b; }, /** * inQuad easing functions * @function inQuad * @memberof ludo.svg.easing * @example * var v = new ludo.View({ * renderTo: document.body, layout:{ width:'matchParent', height:'matchParent' } * }); * var svg = v.svg(); * * var circle = svg.$('circle', { cx: 100, cy: 100, r: 50, fill: '#ff0000' }); * svg.append(circle); * * circle.animate({ * cx:300, cy: 200 * },{ * easing: ludo.svg.easing.inQuad * duration: 1000, * complete:function(){ * console.log('completed'); * } * }); */ inQuad: function (t, b, c, d) { t /= d; return c * t * t + b; }, /** * outQuad easing functions * @function outQuad * @memberof ludo.svg.easing * @example * var v = new ludo.View({ * renderTo: document.body, layout:{ width:'matchParent', height:'matchParent' } * }); * var svg = v.svg(); * * var circle = svg.$('circle', { cx: 100, cy: 100, r: 50, fill: '#ff0000' }); * svg.append(circle); * * circle.animate({ * cx:300, cy: 200 * },{ * easing: ludo.svg.easing.outQuad * duration: 1000, * complete:function(){ * console.log('completed'); * } * }); */ outQuad: function (t, b, c, d) { t /= d; return -c * t * (t - 2) + b; }, /** * inOutQuad easing functions * @function inOutQuad * @memberof ludo.svg.easing * @example * var v = new ludo.View({ * renderTo: document.body, layout:{ width:'matchParent', height:'matchParent' } * }); * var svg = v.svg(); * * var circle = svg.$('circle', { cx: 100, cy: 100, r: 50, fill: '#ff0000' }); * svg.append(circle); * * circle.animate({ * cx:300, cy: 200 * },{ * easing: ludo.svg.easing.inOutQuad * duration: 1000, * complete:function(){ * console.log('completed'); * } * }); */ inOutQuad: function (t, b, c, d) { t /= d / 2; if (t < 1) return c / 2 * t * t + b; t--; return -c / 2 * (t * (t - 2) - 1) + b; }, /** * inCubic easing functions * @function inCubic * @memberof ludo.svg.easing * @example * var v = new ludo.View({ * renderTo: document.body, layout:{ width:'matchParent', height:'matchParent' } * }); * var svg = v.svg(); * * var circle = svg.$('circle', { cx: 100, cy: 100, r: 50, fill: '#ff0000' }); * svg.append(circle); * * circle.animate({ * cx:300, cy: 200 * },{ * easing: ludo.svg.easing.inCubic * duration: 1000, * complete:function(){ * console.log('completed'); * } * }); */ inCubic: function (t, b, c, d) { t /= d; return c * t * t * t + b; }, /** * outCubic easing functions * @function outCubic * @memberof ludo.svg.easing * @example * var v = new ludo.View({ * renderTo: document.body, layout:{ width:'matchParent', height:'matchParent' } * }); * var svg = v.svg(); * * var circle = svg.$('circle', { cx: 100, cy: 100, r: 50, fill: '#ff0000' }); * svg.append(circle); * * circle.animate({ * cx:300, cy: 200 * },{ * easing: ludo.svg.easing.outCubic * duration: 1000, * complete:function(){ * console.log('completed'); * } * }); */ outCubic: function (t, b, c, d) { t /= d; t--; return c * (t * t * t + 1) + b; }, /** * inOutCubic easing functions * @function inOutCubic * @memberof ludo.svg.easing * @example * var v = new ludo.View({ * renderTo: document.body, layout:{ width:'matchParent', height:'matchParent' } * }); * var svg = v.svg(); * * var circle = svg.$('circle', { cx: 100, cy: 100, r: 50, fill: '#ff0000' }); * svg.append(circle); * * circle.animate({ * cx:300, cy: 200 * },{ * easing: ludo.svg.easing.inOutCubic * duration: 1000, * complete:function(){ * console.log('completed'); * } * }); */ inOutCubic: function (t, b, c, d) { t /= d / 2; if (t < 1) return c / 2 * t * t * t + b; t -= 2; return c / 2 * (t * t * t + 2) + b; }, /** * inQuart easing functions * @function inQuart * @memberof ludo.svg.easing * @example * var v = new ludo.View({ * renderTo: document.body, layout:{ width:'matchParent', height:'matchParent' } * }); * var svg = v.svg(); * * var circle = svg.$('circle', { cx: 100, cy: 100, r: 50, fill: '#ff0000' }); * svg.append(circle); * * circle.animate({ * cx:300, cy: 200 * },{ * easing: ludo.svg.easing.inQuart * duration: 1000, * complete:function(){ * console.log('completed'); * } * }); */ inQuart: function (t, b, c, d) { t /= d; return c * t * t * t * t + b; }, /** * outQuart easing functions * @function outQuart * @memberof ludo.svg.easing * @example * var v = new ludo.View({ * renderTo: document.body, layout:{ width:'matchParent', height:'matchParent' } * }); * var svg = v.svg(); * * var circle = svg.$('circle', { cx: 100, cy: 100, r: 50, fill: '#ff0000' }); * svg.append(circle); * * circle.animate({ * cx:300, cy: 200 * },{ * easing: ludo.svg.easing.outQuart * duration: 1000, * complete:function(){ * console.log('completed'); * } * }); */ outQuart: function (t, b, c, d) { t /= d; t--; return -c * (t * t * t * t - 1) + b; }, /** * inOutQuart easing functions * @function inOutQuart * @memberof ludo.svg.easing * @example * var v = new ludo.View({ * renderTo: document.body, layout:{ width:'matchParent', height:'matchParent' } * }); * var svg = v.svg(); * * var circle = svg.$('circle', { cx: 100, cy: 100, r: 50, fill: '#ff0000' }); * svg.append(circle); * * circle.animate({ * cx:300, cy: 200 * },{ * easing: ludo.svg.easing.inOutQuart * duration: 1000, * complete:function(){ * console.log('completed'); * } * }); */ inOutQuart: function (t, b, c, d) { t /= d / 2; if (t < 1) return c / 2 * t * t * t * t + b; t -= 2; return -c / 2 * (t * t * t * t - 2) + b; }, /** * inQuint easing functions * @function inQuint * @memberof ludo.svg.easing * @example * var v = new ludo.View({ * renderTo: document.body, layout:{ width:'matchParent', height:'matchParent' } * }); * var svg = v.svg(); * * var circle = svg.$('circle', { cx: 100, cy: 100, r: 50, fill: '#ff0000' }); * svg.append(circle); * * circle.animate({ * cx:300, cy: 200 * },{ * easing: ludo.svg.easing.inQuint * duration: 1000, * complete:function(){ * console.log('completed'); * } * }); */ inQuint: function (t, b, c, d) { t /= d; return c * t * t * t * t * t + b; }, /** * outQuint easing functions * @function outQuint * @memberof ludo.svg.easing * @example * var v = new ludo.View({ * renderTo: document.body, layout:{ width:'matchParent', height:'matchParent' } * }); * var svg = v.svg(); * * var circle = svg.$('circle', { cx: 100, cy: 100, r: 50, fill: '#ff0000' }); * svg.append(circle); * * circle.animate({ * cx:300, cy: 200 * },{ * easing: ludo.svg.easing.outQuint * duration: 1000, * complete:function(){ * console.log('completed'); * } * }); */ outQuint: function (t, b, c, d) { t /= d; t--; return c * (t * t * t * t * t + 1) + b; }, /** * inOutQuint easing functions * @function inOutQuint * @memberof ludo.svg.easing * @example * var v = new ludo.View({ * renderTo: document.body, layout:{ width:'matchParent', height:'matchParent' } * }); * var svg = v.svg(); * * var circle = svg.$('circle', { cx: 100, cy: 100, r: 50, fill: '#ff0000' }); * svg.append(circle); * * circle.animate({ * cx:300, cy: 200 * },{ * easing: ludo.svg.easing.inOutQuint * duration: 1000, * complete:function(){ * console.log('completed'); * } * }); */ inOutQuint: function (t, b, c, d) { t /= d / 2; if (t < 1) return c / 2 * t * t * t * t * t + b; t -= 2; return c / 2 * (t * t * t * t * t + 2) + b; }, /** * inSine easing functions * @function inSine * @memberof ludo.svg.easing * @example * var v = new ludo.View({ * renderTo: document.body, layout:{ width:'matchParent', height:'matchParent' } * }); * var svg = v.svg(); * * var circle = svg.$('circle', { cx: 100, cy: 100, r: 50, fill: '#ff0000' }); * svg.append(circle); * * circle.animate({ * cx:300, cy: 200 * },{ * easing: ludo.svg.easing.inSine * duration: 1000, * complete:function(){ * console.log('completed'); * } * }); */ inSine: function (t, b, c, d) { return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; }, /** * outSine easing functions * @function outSine * @memberof ludo.svg.easing * @example * var v = new ludo.View({ * renderTo: document.body, layout:{ width:'matchParent', height:'matchParent' } * }); * var svg = v.svg(); * * var circle = svg.$('circle', { cx: 100, cy: 100, r: 50, fill: '#ff0000' }); * svg.append(circle); * * circle.animate({ * cx:300, cy: 200 * },{ * easing: ludo.svg.easing.outSine * duration: 1000, * complete:function(){ * console.log('completed'); * } * }); */ outSine: function (t, b, c, d) { return c * Math.sin(t / d * (Math.PI / 2)) + b; }, /** * outSine easing functions * sinusoidal easing in/out - accelerating until halfway, then decelerating * @function outSine * @memberof ludo.svg.easing * @example * var v = new ludo.View({ * renderTo: document.body, layout:{ width:'matchParent', height:'matchParent' } * }); * var svg = v.svg(); * * var circle = svg.$('circle', { cx: 100, cy: 100, r: 50, fill: '#ff0000' }); * svg.append(circle); * * circle.animate({ * cx:300, cy: 200 * },{ * easing: ludo.svg.easing.outSine * duration: 1000, * complete:function(){ * console.log('completed'); * } * }); */ inOutSine: function (t, b, c, d) { return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b; }, /** * inExpo easing functions * exponential easing in - accelerating from zero velocity * @function inExpo * @memberof ludo.svg.easing * @example * var v = new ludo.View({ * renderTo: document.body, layout:{ width:'matchParent', height:'matchParent' } * }); * var svg = v.svg(); * * var circle = svg.$('circle', { cx: 100, cy: 100, r: 50, fill: '#ff0000' }); * svg.append(circle); * * circle.animate({ * cx:300, cy: 200 * },{ * easing: ludo.svg.easing.inExpo * duration: 1000, * complete:function(){ * console.log('completed'); * } * }); */ inExpo: function (t, b, c, d) { return c * Math.pow(2, 10 * (t / d - 1)) + b; }, /** * outExpo easing functions * exponential easing out - decelerating to zero velocity * @function outExpo * @memberof ludo.svg.easing * @example * var v = new ludo.View({ * renderTo: document.body, layout:{ width:'matchParent', height:'matchParent' } * }); * var svg = v.svg(); * * var circle = svg.$('circle', { cx: 100, cy: 100, r: 50, fill: '#ff0000' }); * svg.append(circle); * * circle.animate({ * cx:300, cy: 200 * },{ * easing: ludo.svg.easing.outExpo * duration: 1000, * complete:function(){ * console.log('completed'); * } * }); */ outExpo: function (t, b, c, d) { return c * ( -Math.pow(2, -10 * t / d) + 1 ) + b; }, /** * inOutExpo easing functions * exponential easing in/out - accelerating until halfway, then decelerating * @function inOutExpo * @memberof ludo.svg.easing * @example * var v = new ludo.View({ * renderTo: document.body, layout:{ width:'matchParent', height:'matchParent' } * }); * var svg = v.svg(); * * var circle = svg.$('circle', { cx: 100, cy: 100, r: 50, fill: '#ff0000' }); * svg.append(circle); * * circle.animate({ * cx:300, cy: 200 * },{ * easing: ludo.svg.easing.inOutExpo * duration: 1000, * complete:function(){ * console.log('completed'); * } * }); */ inOutExpo: function (t, b, c, d) { t /= d / 2; if (t < 1) return c / 2 * Math.pow(2, 10 * (t - 1)) + b; t--; return c / 2 * ( -Math.pow(2, -10 * t) + 2 ) + b; }, /** * inCirc easing functions * circular easing in - accelerating from zero velocity * @function inCirc * @memberof ludo.svg.easing * @example * var v = new ludo.View({ * renderTo: document.body, layout:{ width:'matchParent', height:'matchParent' } * }); * var svg = v.svg(); * * var circle = svg.$('circle', { cx: 100, cy: 100, r: 50, fill: '#ff0000' }); * svg.append(circle); * * circle.animate({ * cx:300, cy: 200 * },{ * easing: ludo.svg.easing.inCirc * duration: 1000, * complete:function(){ * console.log('completed'); * } * }); */ inCirc: function (t, b, c, d) { t /= d; return -c * (Math.sqrt(1 - t * t) - 1) + b; }, /** * outCirc easing functions * circular easing out - decelerating to zero velocity * @function outCirc * @memberof ludo.svg.easing * @example * var v = new ludo.View({ * renderTo: document.body, layout:{ width:'matchParent', height:'matchParent' } * }); * var svg = v.svg(); * * var circle = svg.$('circle', { cx: 100, cy: 100, r: 50, fill: '#ff0000' }); * svg.append(circle); * * circle.animate({ * cx:300, cy: 200 * },{ * easing: ludo.svg.easing.outCirc * duration: 1000, * complete:function(){ * console.log('completed'); * } * }); */ outCirc: function (t, b, c, d) { t /= d; t--; return c * Math.sqrt(1 - t * t) + b; }, /** * inOutCirc easing functions * circular easing in/out - acceleration until halfway, then deceleration * @function inOutCirc * @memberof ludo.svg.easing * @example * var v = new ludo.View({ * renderTo: document.body, layout:{ width:'matchParent', height:'matchParent' } * }); * var svg = v.svg(); * * var circle = svg.$('circle', { cx: 100, cy: 100, r: 50, fill: '#ff0000' }); * svg.append(circle); * * circle.animate({ * cx:300, cy: 200 * },{ * easing: ludo.svg.easing.inOutCirc * duration: 1000, * complete:function(){ * console.log('completed'); * } * }); */ inOutCirc: function (t, b, c, d) { t /= d / 2; if (t < 1) return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b; t -= 2; return c / 2 * (Math.sqrt(1 - t * t) + 1) + b; }, /** * bounce easing functions * @function bounce * @memberof ludo.svg.easing * @example * var v = new ludo.View({ * renderTo: document.body, layout:{ width:'matchParent', height:'matchParent' } * }); * var svg = v.svg(); * * var circle = svg.$('circle', { cx: 100, cy: 100, r: 50, fill: '#ff0000' }); * svg.append(circle); * * circle.animate({ * cx:300, cy: 200 * },{ * easing: ludo.svg.easing.bounce * duration: 1000, * complete:function(){ * console.log('completed'); * } * }); */ bounce: function (t, b, c, d) { var progress = t / d; progress = 1 - ludo.svg.easing._bounce(1 - progress); return c * progress + b; }, /** * bounce easing functions * @function bounce * @memberof ludo.svg.easing * @example * var v = new ludo.View({ * renderTo: document.body, layout:{ width:'matchParent', height:'matchParent' } * }); * var svg = v.svg(); * * var circle = svg.$('circle', { cx: 100, cy: 100, r: 50, fill: '#ff0000' }); * svg.append(circle); * * circle.animate({ * cx:300, cy: 200 * },{ * easing: ludo.svg.easing.outCirc * duration: 1000, * complete:function(){ * console.log('completed'); * } * }); */ bow:function(t,b,c,d){ var progress = ludo.svg.easing._bow(t/d, 1.5); return c * progress + b; }, /** * elastic easing functions * @function elastic * @memberof ludo.svg.easing * @example * var v = new ludo.View({ * renderTo: document.body, layout:{ width:'matchParent', height:'matchParent' } * }); * var svg = v.svg(); * * var circle = svg.$('circle', { cx: 100, cy: 100, r: 50, fill: '#ff0000' }); * svg.append(circle); * * circle.animate({ * cx:300, cy: 200 * },{ * easing: ludo.svg.easing.elastic * duration: 1000, * complete:function(){ * console.log('completed'); * } * }); */ elastic:function(t,b,c,d){ var progress = ludo.svg.easing._elastic(t/d, 1.5); return c * progress + b; }, _elastic:function(progress, x){ return Math.pow(2, 10 * (progress-1)) * Math.cos(20*Math.PI*x/3*progress) }, _bounce: function (progress) { for(var a = 0, b = 1; 1; a += b, b /= 2) { if (progress >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * progress) / 4, 2) + Math.pow(b, 2); } } }, _bow:function(progress, x){ return Math.pow(progress, 2) * ((x + 1) * progress - x) } };