aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSaivan <savian@me.com>2018-05-01 23:06:43 +1000
committerSaivan <savian@me.com>2018-05-01 23:06:43 +1000
commitb5d2b9d1429f01ba992ff6900d9d7a7388ae6dbd (patch)
tree9d38ee839284ae9e1b4676fb6de1288524f289b4
parent6d46e412ae2aeb9c8c75896f74496137f0016cc3 (diff)
downloadsvg.js-b5d2b9d1429f01ba992ff6900d9d7a7388ae6dbd.tar.gz
svg.js-b5d2b9d1429f01ba992ff6900d9d7a7388ae6dbd.zip
More work on the timeline and morphables
-rw-r--r--dirty.html29
-rw-r--r--src/morph.js141
-rw-r--r--src/timeline.js434
3 files changed, 388 insertions, 216 deletions
diff --git a/dirty.html b/dirty.html
index 0c6eac5..0a8b988 100644
--- a/dirty.html
+++ b/dirty.html
@@ -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
+ }
+})