aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bench/runner.html7
-rw-r--r--bench/tests/10000-accesses.js35
-rw-r--r--src/default.js7
-rw-r--r--src/fx.js25
-rw-r--r--src/matrix.js6
-rw-r--r--src/morph.js394
-rw-r--r--src/number.js6
-rw-r--r--src/timeline.js174
8 files changed, 602 insertions, 52 deletions
diff --git a/bench/runner.html b/bench/runner.html
index 965a884..6d1bed2 100644
--- a/bench/runner.html
+++ b/bench/runner.html
@@ -41,13 +41,14 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/snap.svg/0.5.1/snap.svg-min.js"></script>
<script src="svg.bench.js"></script>
<!--<script src="tests/10000-each.js"></script> -->
- <script src="tests/10000-rects.js"></script>
+ <!-- <script src="tests/10000-rects.js"></script>
<script src="tests/10000-circles.js"></script>
<script src="tests/10000-paths.js"></script>
<script src="tests/10000-boxes.js"></script>
- <script src="tests/10000-pointArray-bbox.js"></script>
+ <script src="tests/10000-pointArray-bbox.js"></script> -->
+ <script src="tests/10000-accesses.js"></script>
<script>
SVG.bench.run()
</script>
</body>
-</html> \ No newline at end of file
+</html>
diff --git a/bench/tests/10000-accesses.js b/bench/tests/10000-accesses.js
new file mode 100644
index 0000000..4c7dfea
--- /dev/null
+++ b/bench/tests/10000-accesses.js
@@ -0,0 +1,35 @@
+
+SVG.bench.describe('Access a dom attribues vs dom properties vs object properties', function(bench) {
+ bench.test('using an object', function() {
+ var sum = 0
+ var obj = {x: "30"}
+ for (var i = 0; i < 1000000; i++) {
+ sum += obj.x * i
+ }
+ console.log(sum)
+ })
+
+ bench.test('figure out what the overhead is', function () {
+ var obj = bench.draw.rect(100, 100).move(0, 0)
+ })
+
+ bench.test('using dom attriutes', function () {
+ var sum = 0
+ var obj = bench.draw.rect(100, 100).move(0, 0)
+ var node = obj.node
+ for (var i = 0; i < 1000000; i++) {
+ sum += node.getAttribute('x') * i
+ }
+ console.log(sum, node.getAttribute('x'))
+ })
+
+ bench.test('using dom properties', function () {
+ var sum = 0
+ var obj = bench.draw.rect(100, 100).move(0, 0)
+ var node = obj.node
+ for (var i = 0; i < 1000000; i++) {
+ sum += node.x.baseVal * i
+ }
+ console.log(sum, node.x)
+ })
+})
diff --git a/src/default.js b/src/default.js
index c77b028..f33083c 100644
--- a/src/default.js
+++ b/src/default.js
@@ -1,6 +1,13 @@
SVG.defaults = {
+ // Default animation values
+ timeline: {
+ duration: 600,
+ ease: '>',
+ delay: 0,
+ },
+
// Default attribute values
attrs: {
diff --git a/src/fx.js b/src/fx.js
index c94f747..dd515df 100644
--- a/src/fx.js
+++ b/src/fx.js
@@ -142,12 +142,30 @@ Controlable ()
new Controller(target, controller)
+
+
+Number
+Array
+PathArray
+ViewBox
+PointArray
+Color
+
+
+
+
+
+
+
+
+
+
SVG.Timeline = {
styleAttr (type, name, val) {
- let morpher = new Morph(this.controller).to(val)
+ let morpher = new Morph(val).controller(this.controller)
queue (
()=> {
- morpher = morpher.from(element[type]('name'))
+ morpher = morpher.morph(element[type]('name'))
},
morpher.at
)
@@ -159,7 +177,7 @@ SVG.Timeline = {
let morpher = declarative ? new Controller(target) : new Morph().to(val)
queue (
()=> {
- morpher = morpher.from(element[type]('name'))
+ morpher = morpher.from(element[type](name))
},
() => {
this.element[type](name, morpher.at(pos))
@@ -339,7 +357,6 @@ this.queue(fn, morpher)
new Morph(x(), xGiven)
-
x: function (x, relative) {
if (this.target() instanceof SVG.G) {
this.transform({x: x}, relative)
diff --git a/src/matrix.js b/src/matrix.js
index 1649370..cab657f 100644
--- a/src/matrix.js
+++ b/src/matrix.js
@@ -10,7 +10,11 @@ SVG.Matrix = SVG.invent({
source = source instanceof SVG.Element ? source.matrixify()
: typeof source === 'string' ? arrayToMatrix(source.split(SVG.regex.delimiter).map(parseFloat))
: Array.isArray(source) ? arrayToMatrix(source)
- : typeof source === 'object' ? source
+ : (typeof source === 'object' && (
+ source.a != null || source.b != null || source.c != null
+ || source.d != null || source.e != null || source.f != null
+ )) ? source
+ : (typeof source === 'object') ? new SVG.Matrix().transform(source)
: arguments.length === 6 ? arrayToMatrix([].slice.call(arguments))
: base
diff --git a/src/morph.js b/src/morph.js
new file mode 100644
index 0000000..7e99fef
--- /dev/null
+++ b/src/morph.js
@@ -0,0 +1,394 @@
+SVG.Morphable = SVG.invent{
+ create: function (controller) {
+ this.controller = controller || function (from, to, pos) {
+ return pos < 1 ? from : to
+ }
+ },
+
+ extend: {
+
+ 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
+ return this
+ }
+
+ _set: function (value) {
+
+ if(!this._type) {
+ if (SVG.Color.isColor(val)) {
+ this._type = SVG.Color
+
+ } else if (SVG.regex.delimiter.test(val)) {
+
+ this._type = SVG.regex.pathLetters.test(val)
+ ? SVG.PathArray
+ : SVG.Array
+
+ } else if (SVG.regex.numberAndUnit.test(val)) {
+ this._type = SVG.Number
+
+ } else if (value in SVG.MorphableTypes) {
+ this._type = value.constructor
+
+ // } else if (typeof value == 'object') {
+ // this._type = SVG.Morphable.TransformBag
+ } else {
+ this._type = SVG.Morphable.NonMorphable
+ }
+ }
+
+ return (new this._type(value)).toArray()
+ }
+
+ controller: function (controller) {
+ this._controller = controller
+ }
+
+ at: function (pos) {
+
+ var _this = this
+
+ // for(var i = 0, len = this._from.length; i < len; ++i) {
+ // arr.push(this.controller(this._from[i], this._to[i]))
+ // }
+
+ return this.type.fromArray(this.modifier(this._from.map(function (i, index) {
+ return _this.controller(i, _this._to[i], pos)
+ })))
+ },
+
+ valueOf: function () {
+ return this._value
+ }
+ }
+}
+
+SVG.Morphable.NonMorphable = SVG.invent({
+ create: function (val) {
+ this.value = val
+ },
+
+ extend: {
+ valueOf: function () {
+ return this.value
+ },
+
+ toArray: function () {
+ return [this.value]
+ },
+
+ fromArray: function (arr) {
+ return new SVG.Morphable.NonMorphable(arr[0])
+ }
+ }
+})
+
+SVG.Morphable.TransformBag = SVG.invent({
+ create: function (val) {
+ this.value = new Matrix(val).decompose()
+ },
+
+ extend: {
+ valueOf: function () {
+ return this.value
+ },
+
+ toArray: function (){
+ var v = this.value
+
+ return [
+ v.scaleX,
+ v.scaleY,
+ v.shear,
+ v.rotate,
+ v.translateX,
+ v.translateY
+ ]
+ }
+
+ fromArray: function (arr) {
+ return new SVG.Morphable.TransformBag({
+ scaleX: arr[0],
+ scaleY: arr[1],
+ shear: arr[2],
+ rotate: arr[3],
+ translateX: arr[4],
+ translateY: arr[5]
+ })
+ }
+})
+
+SVG.MorphableTypes = [SVG.Number, SVG.Color, SVG.Box, SVG.Matrix, SVG.Morphable.NonMorphable, SVG.Morphable.TransformBag]
+SVG.extend(SVG.MorphableTypes, {
+ to: (item, args) => {
+ let a = new SVG.Morphable().type(this.constructor).to(item, args)
+ },
+})
+
+
+
+
+// - Objects are just variable bags
+// - morph rerutrns a morphable. No state on normal objects (like SVG.Color)
+// - Objects can be represented as Array (with toArray())
+// - Objects have an unmorph/fromarray function which converts it back to a normal object
+
+// var b = new Color('#fff')
+// b.morph('#000') === new Morph(b).to('#000')
+
+// sweet = Color('#fff')
+// dark = Color('#fef')
+// sweet.to(dark, 'hsl')
+
+// angle = Number(30)
+// lastAngle = Number(300)
+// angle.to(lastAngle, cyclic)
+
+// mat1 = Matrix().transform({rotation: 30, scale: 0})
+// mat2 = Matrix(30, 40, 50, 60, 10, 20)
+// mat1.to(mat2)
+
+
+
+/**
+ ** 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)
+
+
+
+
+el.animate().trasform({rotate: 720, scale: 2}, true)
+
+el.animate().scale(2).rotate(720)
+
+el.animate().transform({origin: traslate, })
+
+absolute -> start at current - {affine params}
+relative -> start at 0 always - {random stuff}
+
+
+ |> object.toArray()
+ |> (_) => _.map(() => {})
+ |> modifier
+ |> fromArray
+
+function transform(transforms, relative, affine) {
+ affine = transforms.affine || affine
+ relative = transforms.relative || relative
+
+ // 1. define the final state (T) and decompose it (once) t = [tx, ty, the, lam, sy, sx]
+ var morpher = new SVG.Morphable.TransformBag().to(transforms)
+
+ // make sure you have an identity matrix defined as default for relative transforms
+ var morpher.from()
+ var el = this.target()
+
+ var initFn = relative ? function() {} : function() {
+ // 2. on every frame: pull the current state of all previous transforms (M - m can change)
+ morpher.from(el.transform())
+ }
+
+ this.queue(initFn, function (pos) {
+ // 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)
+ matrix = matrix.multiply(el.transform().inverse())
+ }
+
+ el.pushTransform(matrix)
+ })
+}
+
+
+SVG.Morphable.TransformList = Object
+
+ if(affine) {
+ var morpher = new Matrix().to(transforms)
+ }
+
+ if(input is typeof plain object) {
+ // deal with a ttransformList
+ this.type = SVG.Morphable.TransformList
+ }
+
+ var morpher = new Morphable(modifier).to(transforms)
+
+ this.queue(() => {
+ morpher.from(this.transform())
+ }, (pos) => {
+ var matrix = morpher.at(pos)
+ el.transform(matrix)
+ })
+
+el.transform({rotate: 720, sclae: 2, })
+
+el.scale(2)
+ .rotate(720)
+
+from -> 300
+to -> [295, 305]
+
+from -> 358
+to -> 1
+
+
+
+from -> 300
+to -> 30
+
+function transform(transforms, affine) {
+
+ if(relative) {
+
+ var morpher = new Morphable().to(transforms, affine)
+ this.queue(() => {}, (pos) => {
+ var matrix = morpher.at(pos)
+ el.transform(matrix)
+ })
+
+ } else {
+
+ this.queue(() => {
+ morpher
+ }, (pos) => {
+ var matrix = morpher.at(pos)
+ el.transform(matrix)
+ })
+
+ }
+}
+
+
+
+// el.animate().rotate(480)
+function rotate(val) { // Relative
+ var morpher = new Morphable().from(0).to(val)
+ this.queue(() => {}, (pos)=> {
+ var rotation = morpher.at(pos)
+ el.rotate(rotation)
+ })
+}
+// morph = new Morphable(0).to(50, [0, 360]) -> in timeline
+//
+//
+//
+// on each frame
+// el.rotate(morph.at(pos))
+
+
+Morph.modifiers = {
+
+ hsb:
+
+
+}
+
+
+new Color(#fff).to('#000', 'hsb')
+
+at returns a matrix anyway
+
+new Number()
+
+
+el.animate().fill('#000', 'hsb')
+function fill(val, colorspace) {
+ var morpher = new Morphable().to(val, colorspace || 'rgb')
+
+ this.queue((val) => {
+ morpher.from(val)
+ }, (pos)=> {
+ var color = morpher.at(pos)
+ el.fill(color)
+ })
+}
+
+//
+// Number.toArray() -> [3]
+// Color.toArray() -> [red, green, blue]
+//
+//
+//
+//
+//
+//
+//
+//
+//
+
+
+
+
+
+
+
+
+
+new Color(30, 50, 40).toArray()
+
+
+
+
+new PathArray([['M', 0, 3], ['L', 4, 5]]).morph(5, 3, 2, 8, 5)
+
+controller = (s, e, p)=> {return s + (e-s) * p}
+
+[['M', 0, 3], ['L', 4, 5], ['A', 120, 120, 1, 0]]
+
+
+['1', '2', '3'] => parseFloat()
+
+
+rect.anim()
+ .color('blue')
+ .anim()
+ .color(new Color('red'))
+
+
+
+a = new SVG.Color('#3f2').to('#5f4').at(0.3)
+
+
+new Morphable('#3f2').to('#5f4').at(0.4)
+
+
+
+
+
+
+
+/*
+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
+}
+*/
diff --git a/src/number.js b/src/number.js
index 6413f94..e6cecc2 100644
--- a/src/number.js
+++ b/src/number.js
@@ -100,5 +100,11 @@ SVG.Number = SVG.invent({
.plus(this)
}
+
+
+new SVG.Color('#2a4e5a').morph('#3b4f2a').at(0.4)
+
+new Morph().from('#2a4e5a').to('#3b4f2a').at(0.3)
+
}
})
diff --git a/src/timeline.js b/src/timeline.js
index 035c0f7..5106058 100644
--- a/src/timeline.js
+++ b/src/timeline.js
@@ -11,9 +11,9 @@ SVG.easing = {
function Runner (timeline) {
// We store a reference to the function to run and the timeline to use
- this.functions = []
this.timeline = timeline
this.transforms = []
+ this.functions = []
this.done = false
// We copy the current values from the timeline because they can change
@@ -29,11 +29,10 @@ Runner.prototype = {
this.functions.push(fn)
},
- run: function (time) {
-
- var line = this.timeline
+ step: function (time) {
// If it is time to do something, act now.
+ var end = this._start + this._duration
var running = this._start < time && time < end
if (running && this._running) {
var position = (time - this._startTime) / this._duration
@@ -50,7 +49,7 @@ Runner.prototype = {
closure.finished = !running
},
- stop: function () {
+ snap: function () {
},
@@ -60,44 +59,128 @@ Runner.prototype = {
}
+let time = window.performance || window.Date
+
+
SVG.Timeline = SVG.invent({
- create: function (o, easy, delay, epoch) {
+ // Construct a new timeline on the given element
+ create: function (element) {
- this.baseTransform = []
- this.runners = []
- this.controller = null
+ // Store a reference to the element to call its parent methods
+ this._element = element
- if(o instanceof 'function') {
- this.controller = o
+ // Store the timing variables
+ this._startTime = time.now()
+ this._duration = SVG.defaults.duration
+ this._ease = SVG.defaults.ease
- } else if (typeof o === 'object') {
- ease = o.ease
- delay = o.delay
- o = o.duration
- }
+ // Play control variables control how the animation proceeds
+ this._controller = o instanceof 'function' ? o : null
+ this._backwards = false
+ this._reverse = false
+ this._loops = 0
- this.ease = ease
- this.delay = delay
- this.duration = o
+ // Keep track of the running animations and their starting parameters
+ this._baseTransform = null
+ this._running = true
+ this._runners = []
},
extend: {
- animate (duration, ease, delay, epoch)
- loop (times, reverse)
- duration (time)
- delay (by, epoch)
- ease (fn)
-
- play ()
- pause ()
- stop ()
- finish (all=true)
- speed (newSpeed)
- seek (dt)
- persist (dt || forever) // 0 by default
- reverse ()
+ animate (duration, delay, now) {
+
+ // Clear the controller and the looping parameters
+ this._controller = null
+ this._backwards = false
+ this._swing = false
+ this._loops = 0
+
+ // If we have a controller, we will use the declarative animation mode
+ if(duration instanceof 'function') {
+
+ this._controller = duration
+
+ // If we have an object we are declaring imperative animations
+ } else if (typeof duration === 'object') {
+
+ ease = duration.ease
+ delay = duration.delay
+ now = duration.now
+ duration = duration.duration
+ }
+
+ // We start the next animation after the old one is complete
+ this._startTime = now ? time.now() : (this._startTime + this._duration)
+ this._duration = duration || SVG.defaults.duration
+
+ // Make a new runner to take care of the
+
+ return this
+ },
+
+ duration (time) {
+ return this.animate(time, 0, false)
+ },
+
+ delay (by, now) {
+ return this.animate(0, by, now)
+ },
+
+ ease (fn) {
+ this._ease = SVG.easing[fn || SVG.defaults.ease] || fn
+ return this
+ },
+
+ loop (times, swing) {
+ this._loops = times
+ this._swing = swing
+ },
+
+ play () {
+ this._running = true
+ this._continue()
+ },
+
+ pause () {
+ this._running = false
+ },
+
+ stop () {
+ this.pause()
+
+ // Cancel all of the requested animation frames
+
+ },
+
+ finish (all=true) {
+
+ },
+
+ speed (newSpeed) {
+
+ },
+
+ seek (dt) {
+
+ },
+
+ persist (dt || forever) {
+ // 0 by default
+ },
+
+ reverse () {
+
+ },
+
+ queue (initialise, during) {
+
+ // Make a new runner
+ var runner = new Runner(this)
+ this._runners.push()
+
+ },
_step (dt) {
@@ -105,23 +188,26 @@ SVG.Timeline = SVG.invent({
// Checks if we are running and continues the animation
_continue () {
- , continue: function () {
- if (this.paused) return
- if (!this.nextFrame)
- this.step()
- return this
- }
+ if (this._paused) return
+
+ // Go through each of the runners and step them
+ },
- }
},
+ // Only elements are animatable
+ parent: SVG.Element,
+ // These methods will be added to all SVG.Element objects
construct: {
- animate: function(o, ease, delay, epoch) {
- return (this.timeline = this.timeline || new SVG.Timeline(o, ease, delay, epoch))
+ animate: function(o, delay, now) {
+
+ // Get the current timeline or construct a new one
+ this.timeline = (this.timeline || new SVG.Timeline(this))
+ .animate(o, delay, now)
+ return this.timeline
}
}
-
}
// Extend the attribute methods separately to avoid cluttering the main
@@ -150,7 +236,7 @@ SVG.extend(SVG.Timeline, {
this.queue(
function () {
- morpher = morpher.from(element[type]('name'))
+ morpher = morpher.from(element[type](name))
},
function () {
this.element[type](name, morpher.at(pos))