diff options
author | Saivan <savian@me.com> | 2018-04-23 20:25:41 +1000 |
---|---|---|
committer | Saivan <savian@me.com> | 2018-04-23 20:25:41 +1000 |
commit | 64b3144c89247d0be176bf01c28aa5fe6bef84d5 (patch) | |
tree | 1cb689a600c7804ee41eef66d63fb4f104c85e60 | |
parent | 7a4979d0cfd4c4dbe91b53b69c62aa28c295af5c (diff) | |
download | svg.js-64b3144c89247d0be176bf01c28aa5fe6bef84d5.tar.gz svg.js-64b3144c89247d0be176bf01c28aa5fe6bef84d5.zip |
Added some timeline and Morphing functions
-rw-r--r-- | bench/runner.html | 7 | ||||
-rw-r--r-- | bench/tests/10000-accesses.js | 35 | ||||
-rw-r--r-- | src/default.js | 7 | ||||
-rw-r--r-- | src/fx.js | 25 | ||||
-rw-r--r-- | src/matrix.js | 6 | ||||
-rw-r--r-- | src/morph.js | 394 | ||||
-rw-r--r-- | src/number.js | 6 | ||||
-rw-r--r-- | src/timeline.js | 174 |
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: { @@ -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)) |