diff options
author | Saivan <savian@me.com> | 2018-05-01 23:06:43 +1000 |
---|---|---|
committer | Saivan <savian@me.com> | 2018-05-01 23:06:43 +1000 |
commit | b5d2b9d1429f01ba992ff6900d9d7a7388ae6dbd (patch) | |
tree | 9d38ee839284ae9e1b4676fb6de1288524f289b4 | |
parent | 6d46e412ae2aeb9c8c75896f74496137f0016cc3 (diff) | |
download | svg.js-b5d2b9d1429f01ba992ff6900d9d7a7388ae6dbd.tar.gz svg.js-b5d2b9d1429f01ba992ff6900d9d7a7388ae6dbd.zip |
More work on the timeline and morphables
-rw-r--r-- | dirty.html | 29 | ||||
-rw-r--r-- | src/morph.js | 141 | ||||
-rw-r--r-- | src/timeline.js | 434 |
3 files changed, 388 insertions, 216 deletions
@@ -5,13 +5,14 @@ <meta charset="utf-8"> <title></title> <script type="text/javascript" src="dist/svg.js"></script> + <script type="text/javascript" src="src/morph.js"></script> <script type="text/javascript" src="src/timeline.js"></script> </head> -<body> +<body style="background-color: #c7c7ff"> <!-- Making the svg --> -<svg width=1000px height=1000px> - <rect x=50 y=100 width=200 height=100 fill=blue /> +<svg width=1000px height=1000px > + <rect x=50 y=100 width=200 height=100 stroke=white stroke-width=2 /> </svg> <!-- Modifying the svg --> @@ -21,20 +22,22 @@ let rect = SVG('rect') let {sin, PI: pi, round, sqrt} = Math +function getColor(t) { + let a = round(80 * sin(2 * pi * t / 0.5 + 0.01) + 150) + let b = round(50 * sin(2 * pi * t / 0.5 + 4.6) + 200) + let c = round(100 * sin(2 * pi * t / 0.5 + 2.3) + 150) + let color = new SVG.Color(`rgb(${a}, ${b}, ${c})`).toString() + return color +} + rect.animate(2000) .queue( - () => {}, + () => {rect.attr('fill', getColor(0))}, t => rect.transform({scale: sqrt(t), rotate: 720 * t}) ) - .animate(2000, 1000, true) - .queue(()=> {}, - t => { - let a = round(125 * sin(2 * pi * t / 0.5 + 0.01) + 125) - let b = round(125 * sin(2 * pi * t / 0.5 + 2.3) + 125) - let c = round(125 * sin(2 * pi * t / 0.5 + 4.6) + 125) - let color = new SVG.Color(`rgb(${a}, ${b}, ${c})`).toString() - rect.attr('fill', color) - }) + .animate(1500, 500, true) + .queue(()=> {}, t => rect.attr('fill', getColor(t))) + .animate(1000).move(200, 200).size(300, 300) // setTimeout(()=> { rect.animate().pause() }, 500) // setTimeout(()=> { rect.animate().play() }, 2000) diff --git a/src/morph.js b/src/morph.js index c31f0cb..7b31dcd 100644 --- a/src/morph.js +++ b/src/morph.js @@ -1,4 +1,4 @@ -SVG.Morphable = SVG.invent{ +SVG.Morphable = SVG.invent({ create: function (controller) { // FIXME: the default controller does not know about easing this.controller = controller || function (from, to, pos) { @@ -11,13 +11,13 @@ SVG.Morphable = SVG.invent{ from: function (val) { this._from = this._set(val) return this - } + }, to: function (val, modifier) { this._to = this._set(val) this.modifier = modifier return this - } + }, type: function (type) { this._type = type @@ -29,7 +29,7 @@ SVG.Morphable = SVG.invent{ } } return this - } + }, _set: function (value) { @@ -55,11 +55,11 @@ SVG.Morphable = SVG.invent{ } return (new this._type(value)).toArray() - } + }, controller: function (controller) { this._controller = controller - } + }, at: function (pos) { @@ -80,7 +80,7 @@ SVG.Morphable = SVG.invent{ return this._value } } -} +}) SVG.Morphable.NonMorphable = SVG.invent({ create: function (val) { @@ -123,7 +123,7 @@ SVG.Morphable.TransformBag = SVG.invent({ v.translateX, v.translateY ] - } + }, fromArray: function (arr) { return new SVG.Morphable.TransformBag({ @@ -134,6 +134,7 @@ SVG.Morphable.TransformBag = SVG.invent({ translateX: arr[4], translateY: arr[5] }) + } } }) @@ -156,7 +157,7 @@ SVG.Morphable.ObjectBag = SVG.invent({ toArray: function (){ return this.values - } + }, fromArray: function (arr) { var obj = {} @@ -167,6 +168,7 @@ SVG.Morphable.ObjectBag = SVG.invent({ return obj } + } }) SVG.MorphableTypes = [ @@ -174,6 +176,9 @@ SVG.MorphableTypes = [ SVG.Color, SVG.Box, SVG.Matrix, + SVG.Array, + SVG.PointArray, + SVG.PathArray, SVG.Morphable.NonMorphable, SVG.Morphable.TransformBag, SVG.Morphable.ObjectBag, @@ -233,19 +238,6 @@ SVG.extend(SVG.MorphableTypes, { -/** - ** absolute transformations - **/ - -// M v -----|-----(D M v = I v)------|-----> T v -// -// 1. define the final state (T) and decompose it (once) t = [tx, ty, the, lam, sy, sx] -// 2. on every frame: pull the current state of all previous transforms (M - m can change) -// and then write this as m = [tx0, ty0, the0, lam0, sy0, sx0] -// 3. Find the interpolated matrix I(pos) = m + pos * (t - m) -// - Note I(0) = M -// - Note I(1) = T -// 4. Now you get the delta matrix as a result: D = I * inv(M) // C R x = D C x = A x @@ -262,80 +254,7 @@ absolute -> start at current - {affine params} relative -> start at 0 always - {random stuff} */ -function transform(o, relative, affine) { - affine = transforms.affine || affine || !!transform.a - relative = transforms.relative || relative || false - - var morpher - var el = this.target() - - /** - The default of relative is false - affine defaults to true if transformations are used and to false when a matrix is given - We end up with 4 possibilities: - false, false: absolute direct matrix morph with SVG.Matrix - true, false: relative direct matrix morph with SVG.Marix or relative whatever was passed transformation with ObjectBag - - false, true: absolute affine transformation with SVG.TransformBag - true, true: relative whatever was passed transformation with ObjectBag - **/ - - - // if we have a relative transformation and its not a matrix - // we morph all parameters directly with the ObjectBag - // the following cases are covered here: - // - true, false with ObjectBag - // - true, true with ObjectBag - if(relative && transforms.a == null) { - morpher = SVG.Morphable.ObjectBag(formatTransforms({})) - .to(formatTransforms(transforms)) - .controller(this.controller) - - return this.queue(function() {}, function (pos) { - el.pushRightTransform(new Matrix(morpher.at(pos))) - }) - } - - - // what is left is affine morphing for SVG.Matrix and absolute transformations with TransformBag - // also non affine direct and relative morhing with SVG.Matrix - // the following cases are covered here: - // - false, true with SVG.Matrix - // - false, true with SVG.TransformBag - // - true, false with SVG.Matrix - // - false, false with SVG.Matrix - - // 1. define the final state (T) and decompose it (once) t = [tx, ty, the, lam, sy, sx] - var morpher = (transforms.a && !affine) - ? new SVG.Matrix().to(transforms) - : new SVG.Morphable.TransformBag().to(transforms) - - morpher.controller(this.controller) - - // create identity Matrix for relative not affine Matrix transformation - morpher.from() - - this.queue(function() {}, function (pos) { - - // 2. on every frame: pull the current state of all previous transforms (M - m can change) - var curr = el.currentTransform() - if(!relative) morpher.from(curr) - - // 3. Find the interpolated matrix I(pos) = m + pos * (t - m) - // - Note I(0) = M - // - Note I(1) = T - var matrix = morpher.at(pos) - - if(!relative) { - // 4. Now you get the delta matrix as a result: D = I * inv(M) - var delta = matrix.multiply(curr.inverse()) - el.pushLeftTransform(delta) - } else { - el.pushRightTransform(matrix) - } - }) -} @@ -412,35 +331,3 @@ s| --------B--------- t| ---------C------- **/ - - - - -// el.animate().fill('#000', 'hsb') -function fill(val, colorspace) { - var modifier = Morpher.modifiers[colorspace || 'rgb'] || colorspace - var morpher = new Morphable().to(val, modifier) - - this.queue(function () => { - morpher.from() - }, (pos)=> { - var color = morpher.at(pos) - el.fill(color) - }) -} - - - -// el.animate().zoom(level, {x:100, y:100}) -function zoom(level, point) { - var morpher = SVG.Number().to(level).controller(this.controller) - var el = this.target() - - this.queue(function() { - morpher = morpher.from(element.zoom()) - }, function (pos) { - el.zoom(morpher.at(pos), point) - }) - - return this -} diff --git a/src/timeline.js b/src/timeline.js index 8af678d..07dca45 100644 --- a/src/timeline.js +++ b/src/timeline.js @@ -17,8 +17,9 @@ function Runner (timeline) { // We copy the current values from the timeline because they can change this._timeline = timeline - this._startTime = timeline._startTime + this._start = timeline._startTime this._duration = timeline._duration + this._last = 0 this._active = false // TODO: Think about looping and how to use the runner @@ -29,7 +30,6 @@ Runner.prototype = { add: function (initFn, runFn, alwaysInitialise) { this.functions.push({ - initialised: false, alwaysInitialise: alwaysInitialise || false, initialiser: initFn, runner: runFn, @@ -39,41 +39,57 @@ Runner.prototype = { step: function (time) { // If it is time to do something, act now. - var end = this._startTime + this._duration - var running = (this._startTime < time && time < end) || !this._duration + var end = this._start + this._duration + var timeInside = this._start < time && time < end + var running = timeInside || !this._duration + var allDone = running - // If its time run the animation, we do so - var allDone = time > end - if (running && !this._timeline._paused) { + // If we don't have a duration, we are in declarative mode - // Get the current position for the current animation - // TODO: Deal with looping - var position = (time - this._startTime) / this._duration + // If the time is inside the bounds, run all of the + if (timeInside) { - // We run all of the functions - for (var i = 0, len = this.functions.length; i < len ; ++i) { + // Work out if we need to do the first initialisation + var rising = this._last < this._start + if (rising) { - // Get the current queued item - var current = this.functions[i] + } - // Work out if we need to initialise, and do so if we do - var initialise = current.alwaysInitialise || !current.initialised - if (initialise) { - current.initialiser(position) - } + } else { + + // Work out if we just finished + var justFinished = this._start < this._last && this._last < end + if (justFinished) { - // Run the function required - // TODO: Figure out what declarative needs that it doesn't have - var stillRunning = current.runner(position) - if (stillRunning) { - allDone = false - } } } - // Tell the caller whether this animation is finished return allDone }, + + initialise: function (time) { + + }, + + run: function (type, time) { + + // We run all of the functions + var stillGoing = false + for (var i = 0, len = this.functions.length; i < len ; ++i) { + + // Get the current queued item + var current = this.functions[i][type] + + // Work out if we need to initialise, and do so if we do + var initialise = current.alwaysInitialise + if (initialise) { + current.initialiser(position) + } + + // Run the functions + + } + }, } @@ -157,9 +173,6 @@ SVG.Timeline = SVG.invent({ }, play () { - -console.log("hello"); - this._paused = false this._continue() return this @@ -204,8 +217,6 @@ console.log("hello"); _step (time) { - -console.log("going", this._paused); // If we are paused, just exit if (this._paused) return @@ -284,48 +295,319 @@ console.log("going", this._paused); } }) -// // Extend the attribute methods separately to avoid cluttering the main -// // Timeline class above -// SVG.extend(SVG.Timeline, { -// -// -// attr: function (a, v) { -// return this.styleAttr('attr', a, v) -// }, -// -// // Add animatable styles -// css: function (s, v) { -// return this.styleAttr('css', s, v) -// }, -// -// styleAttr (type, name, val) { -// // apply attributes individually -// if (typeof name === 'object') { -// for (var key in val) { -// this.styleAttr(type, key, val[key]) -// } -// } -// -// var morpher = new Morphable(this.controller).to(val) -// -// this.queue( -// function () { -// morpher = morpher.from(element[type](name)) -// }, -// function () { -// this.element[type](name, morpher.at(pos)) -// } -// ) -// -// return this -// }, -// -// zoom(level, point) { -// let morpher = SVG.Number(level).controller(this.controller) -// this.queue( -// () => {morpher = morpher.from(element.zoom())}, -// (pos) => {element.zoom(morpher.at(pos), point)} -// ) -// return this -// } -// }) + +// Extend the attribute methods separately to avoid cluttering the main +// Timeline class above +SVG.extend(SVG.Timeline, { + + + attr: function (a, v) { + return this.styleAttr('attr', a, v) + }, + + // Add animatable styles + css: function (s, v) { + return this.styleAttr('css', s, v) + }, + + styleAttr (type, name, val) { + // apply attributes individually + if (typeof name === 'object') { + for (var key in val) { + this.styleAttr(type, key, val[key]) + } + } + + var morpher = new Morphable(this.controller).to(val) + + this.queue( + function () { + morpher = morpher.from(element[type](name)) + }, + function () { + this.element[type](name, morpher.at(pos)) + } + ) + + return this + }, + + zoom: function (level, point) { + var morpher = SVG.Number().to(level).controller(this.controller) + var el = this.target() + + this.queue(function() { + morpher = morpher.from(element.zoom()) + }, function (pos) { + el.zoom(morpher.at(pos), point) + }) + + return this + }, + + /** + ** absolute transformations + **/ + + // M v -----|-----(D M v = I v)------|-----> T v + // + // 1. define the final state (T) and decompose it (once) t = [tx, ty, the, lam, sy, sx] + // 2. on every frame: pull the current state of all previous transforms (M - m can change) + // and then write this as m = [tx0, ty0, the0, lam0, sy0, sx0] + // 3. Find the interpolated matrix I(pos) = m + pos * (t - m) + // - Note I(0) = M + // - Note I(1) = T + // 4. Now you get the delta matrix as a result: D = I * inv(M) + + transform: function (o, relative, affine) { + affine = transforms.affine || affine || !!transform.a + relative = transforms.relative || relative || false + + var morpher + var el = this.target() + + /** + The default of relative is false + affine defaults to true if transformations are used and to false when a matrix is given + + We end up with 4 possibilities: + false, false: absolute direct matrix morph with SVG.Matrix + true, false: relative direct matrix morph with SVG.Marix or relative whatever was passed transformation with ObjectBag + + false, true: absolute affine transformation with SVG.TransformBag + true, true: relative whatever was passed transformation with ObjectBag + **/ + + + // if we have a relative transformation and its not a matrix + // we morph all parameters directly with the ObjectBag + // the following cases are covered here: + // - true, false with ObjectBag + // - true, true with ObjectBag + if(relative && transforms.a == null) { + morpher = SVG.Morphable.ObjectBag(formatTransforms({})) + .to(formatTransforms(transforms)) + .controller(this.controller) + + return this.queue(function() {}, function (pos) { + el.pushRightTransform(new Matrix(morpher.at(pos))) + }) + } + + + // what is left is affine morphing for SVG.Matrix and absolute transformations with TransformBag + // also non affine direct and relative morhing with SVG.Matrix + // the following cases are covered here: + // - false, true with SVG.Matrix + // - false, true with SVG.TransformBag + // - true, false with SVG.Matrix + // - false, false with SVG.Matrix + + // 1. define the final state (T) and decompose it (once) t = [tx, ty, the, lam, sy, sx] + var morpher = (transforms.a && !affine) + ? new SVG.Matrix().to(transforms) + : new SVG.Morphable.TransformBag().to(transforms) + + morpher.controller(this.controller) + + // create identity Matrix for relative not affine Matrix transformation + morpher.from() + + this.queue(function() {}, function (pos) { + + // 2. on every frame: pull the current state of all previous transforms (M - m can change) + var curr = el.currentTransform() + if(!relative) morpher.from(curr) + + // 3. Find the interpolated matrix I(pos) = m + pos * (t - m) + // - Note I(0) = M + // - Note I(1) = T + var matrix = morpher.at(pos) + + if(!relative) { + // 4. Now you get the delta matrix as a result: D = I * inv(M) + var delta = matrix.multiply(curr.inverse()) + el.pushLeftTransform(delta) + } else { + el.pushRightTransform(matrix) + } + }) + + return this + }, + + // Animatable x-axis + x: function (x, relative) { + var morpher = new SVG.Number().to(x) + + /* + if (this.target() instanceof SVG.G) { + this.transform({x: x}, relative) + return this + } + */ + + this.queue(function () { + var from = this._element.x() + morpher.from(from) + if(relative) morpher.to(from + x) + }, function (pos) { + this._element.x(morpher.at(pos)) + }) + + return this + }, + + // Animatable y-axis + y: function (y, relative) { + var morpher = new SVG.Number().to(y) + + /* + if (this.target() instanceof SVG.G) { + this.transform({y: y}, relative) + return this + } + */ + + this.queue(function () { + var from = this._element.y() + morpher.from(from) + if(relative) morpher.to(from + y) + }, function (pos) { + this._element.y(morpher.at(pos)) + }) + + return this + }, + + _queueObject: function (method, to) { + var morpher = new SVG.Morphable(this.controller).to(to) + + this.queue(function () { + morpher.from(this._element[method]()) + }, function () { + this._element[method](morpher.at(pos)) + }) + + return this + }, + + _queueNumber: function (method, value) { + return this._queueObject(method, new Number(value)) + }, + + // Animatable center x-axis + cx: function (x) { + return this._queueNumber('cx', x) + }, + + // Animatable center y-axis + cy: function (y) { + return this._queueNumber('cy', x) + }, + + // 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) { + // animate bbox based size for all other elements + var box + + if (!width || !height) { + box = this._element().bbox() + } + + if (!width) { + width = box.width / box.height * height + } + + if (!height) { + height = box.height / box.width * width + } + + return this + .width(width) + .height(height) + }, + + // Add animatable width + width: function (width) { + return this._queueNumber('width', width) + }, + + // Add animatable height + height: function (height) { + return this._queueNumber('height', height) + }, + + // Add animatable plot + plot: function (a, b, c, d) { + // Lines can be plotted with 4 arguments + if (arguments.length === 4) { + return this.plot([a, b, c, d]) + } + + return this._queueObject('plot', new this._element.morphArray(a)) + + /*var morpher = this._element.morphArray().to(a) + + this.queue(function () { + morpher.from(this._element.array()) + }, function (pos) { + this._element.plot(morpher.at(pos)) + }) + + return this*/ + }, + + // Add leading method + leading: function (value) { + return this._element.leading + ? this._queueNumber('leading', value) + : this + }, + + // Add animatable viewbox + viewbox: function (x, y, width, height) { + if (this._element instanceof SVG.Container) { + this._queueObject('viewbox', new SVG.Box(x, y, width, height)) + + /*var morpher = new SVG.Box().to(x, y, width, height) + + this.queue(function () { + morpher.from(this._element.viewbox()) + }, function (pos) { + this._element.viewbox(morpher.at(pos)) + }) + + return this*/ + } + + return this + }, + update: function (o) { + if (this._element instanceof SVG.Stop) { + if (typeof o !== 'object') { + 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 + } +}) |