diff options
-rw-r--r-- | dirty.html | 92 | ||||
-rw-r--r-- | spec/spec/runner.js | 112 | ||||
-rw-r--r-- | src/morph.js | 6 | ||||
-rw-r--r-- | src/number.js | 3 | ||||
-rw-r--r-- | src/runner.js | 141 | ||||
-rw-r--r-- | src/timeline.js | 1 | ||||
-rw-r--r-- | useCases.md | 3 |
7 files changed, 215 insertions, 143 deletions
@@ -12,6 +12,9 @@ </head> <body style="background-color: #c7c7ff"> +<div id="absolute"><label>Absolute: <input type="range" min="0" max="1" step="0.01"></slider></label><span></span></div> +<div id="position"><label>Position: <input type="range" min="0" max="5" step="0.01"></slider></label><span></span></div> + <!-- Making the svg --> <svg width=1000px height=1000px > <rect x=50 y=100 width=200 height=100 stroke=none stroke-width=2 /> @@ -31,6 +34,25 @@ function getColor(t) { return color } +var rect1 = SVG('<rect>').addTo('svg').size(50, 50).move(100, 100) +var rect2 = SVG('<rect>').addTo('svg').size(50, 50).move(100, 200) + +var anim1 = new SVG.Runner(1000).element(rect1).loop(5, true, 1000).move(200, 100) +var anim2 = new SVG.Runner(1000).element(rect2).loop(5, true, 1000).move(200, 200) + +SVG('#absolute').on('input slide', function (e) { + var val = e.target.value + document.querySelector('#absolute span').textContent = val + anim1.absolute(val) +}) + +SVG('#position').on('input slide', function (e) { + var val = e.target.value + document.querySelector('#position span').textContent = val + anim2.position(val) +}) + + // rect.animate(4000) // .during(t => rect.transform({scale: sqrt(1 + t), rotate: 720 * t})) // .after(500, ()=> {rect.attr('stroke', 'white')}) @@ -52,27 +74,25 @@ function getColor(t) { // .move(200, 200) +for (let i = 0 ; i < 15; i++) { + for (let j = 0 ; j < 10; j++) { + // Make the rect + let o = i + j + let rect = SVG('rect').clone().show() + .width(40).height(40) + .x(50 * i).y(50 * j) + .attr('fill', getColor(o * 0.1)) -// for (let i = 0 ; i < 15; i++) { -// for (let j = 0 ; j < 10; j++) { -// -// // Make the rect -// let o = i + j -// let rect = SVG('rect').clone().show() -// .width(40).height(40) -// .x(50 * i).y(50 * j) -// .attr('fill', getColor(o * 0.1)) -// -// // Move the rect -// let {cx, cy} = rect.bbox() -// -// // Animate the rect -// rect.animate(3000, Math.random() * 2000) -// .during(t => rect.transform({rotate: 720 * t, origin: [cx, cy]})) -// .during(t => rect.attr('fill', getColor(o * 0.1 + t))) -// } -// } + // Move the rect + let {cx, cy} = rect.bbox() + + // Animate the rect + rect.animate(3000, Math.random() * 2000) + .during(t => rect.transform({rotate: 700 * t, origin: [cx, cy]})) + .during(t => rect.attr('fill', getColor(o * 0.1 + t))) + } +} // var bla = SVG('<rect>').size(0, 0).move(200, 200).addTo('svg') // bla.animate().size(220, 200).queue(null, console.log) @@ -124,16 +144,36 @@ function getColor(t) { -// var circle = SVG('<circle>').addTo('svg').size(100, 100).center(200, 200) -// var runner = circle.animate(2000) -// .loop(Infinity, true, 2000) +// var circle = SVG('<rect>').addTo('svg').size(100, 100).center(200, 200) +// var runner = circle.animate(1000) +// .loop(3, true, 500) +// .reverse() // .ease('<>') // .center(500, 200) +// .during(t => circle.transform({rotate: 720 * t, origin: [200, 200]})) -var r = new SVG.Runner(200)//.loop(3, false, 100) -r.queue(null, console.log) - -new SVG.Timeline().schedule(r) +// var r = new SVG.Runner(1000).loop(10, false, 100) +// r.queue(null, console.log) +// +// r.step(1200) // should be 0.1s +// r.step(-300) // should be 0.9s + + +// let recy = SVG('<rect>').addTo('svg').size(100, 100).center(200, 200) +// var runner = recy +// .animate(2000) +// .transform({rotate: 300}, true) +// .transform({translate: [200, 100]}, true) +// .animate(2000, 1000, 'absolute') +// .transform({scale: 2}) +// .animate(2000, 2000, 'absolute') +// .transform({rotate: -300}, true) + +// transform(SVG.Matrix()) // should be affine +// transform(SVG.Matrix(), true) // should be relative +// transform(SVG.Matrix(), true, false) // should relative and nonaffine +// transform({...}) and transform({...}, true) // should animate parameters absolute or relative +// transform({...}, ..., false) // r.step(300) // should be 0.1 // r.step(2 * 1100) // should be 0 diff --git a/spec/spec/runner.js b/spec/spec/runner.js index 126ece4..5fd1376 100644 --- a/spec/spec/runner.js +++ b/spec/spec/runner.js @@ -48,18 +48,6 @@ describe('SVG.Runner', function () { }) }) - describe('loop()', function () { - it('calls animate with correct parameters', function () { - var element = SVG('<rect>') - - spyOn(element, 'animate') - element.loop() - expect(element.animate).toHaveBeenCalledWith(jasmine.objectContaining({ - times: Infinity - })); - }) - }) - describe('delay()', function () { it('calls animate with correct parameters', function () { var element = SVG('<rect>') @@ -76,9 +64,10 @@ describe('SVG.Runner', function () { var runner = new SVG.Runner() runner.queue(initFn, runFn, true) - expect(runner._functions[0]).toEqual(jasmine.objectContaining({ + expect(runner._queue[0]).toEqual(jasmine.objectContaining({ alwaysInitialise: true, initialiser: initFn, + initialised: false, runner: runFn })) }) @@ -128,6 +117,11 @@ describe('SVG.Runner', function () { describe('step()', function () { + it('returns itself', function () { + var runner = new SVG.Runner() + expect(runner.step()).toBe(runner) + }) + it('calls initFn once and runFn at every step when alwaysInitialise is false', function() { var runner = new SVG.Runner() runner.queue(initFn, runFn, false) @@ -154,19 +148,11 @@ describe('SVG.Runner', function () { expect(runFn.calls.count()).toBe(2) }) - it('returns false if not finished', function () { - var runner = new SVG.Runner() - runner.queue(initFn, runFn, false) - - expect(runner.step()).toBe(false) - }) - - it('returns true if finished', function () { - var runner = new SVG.Runner() - runner.queue(initFn, runFn, false) - - expect(runner.step(SVG.defaults.timeline.duration)).toBe(true) - }) + function getLoop(r) { + var loopDuration = r._duration + r._wait + var loopsDone = Math.floor(r._time / loopDuration) + return loopsDone + } // step in time it('steps forward a certain time', function () { @@ -176,27 +162,36 @@ describe('SVG.Runner', function () { r.step(300) // should be 0.3s expect(spy).toHaveBeenCalledWith(0.3) - expect(r._loopsDone).toBe(0) + expect(getLoop(r)).toBe(0) r.step(300) // should be 0.6s expect(spy).toHaveBeenCalledWith(0.6) - expect(r._loopsDone).toBe(0) + expect(getLoop(r)).toBe(0) r.step(600) // should be 0.1s expect(spy).toHaveBeenCalledWith(0.1) - expect(r._loopsDone).toBe(1) + expect(getLoop(r)).toBe(1) r.step(-300) // should be 0.9s expect(spy).toHaveBeenCalledWith(0.9) - expect(r._loopsDone).toBe(0) + expect(getLoop(r)).toBe(0) r.step(2000) // should be 0.7s expect(spy).toHaveBeenCalledWith(0.7) - expect(r._loopsDone).toBe(2) + expect(getLoop(r)).toBe(2) r.step(-2000) // should be 0.9s expect(spy).toHaveBeenCalledWith(0.9) - expect(r._loopsDone).toBe(0) + expect(getLoop(r)).toBe(0) + }) + + it('handles dts which are bigger than the animation time', function () { + var runner = new SVG.Runner(1000) + runner.queue(initFn, runFn, true) + + runner.step(1100) + expect(initFn).toHaveBeenCalled() + expect(runFn).toHaveBeenCalledWith(1) }) }) @@ -206,7 +201,7 @@ describe('SVG.Runner', function () { expect(runner.active()).toBe(true) }) - it('disabled the runner when false is passed', function () { + it('disables the runner when false is passed', function () { var runner = new SVG.Runner() expect(runner.active(false)).toBe(runner) expect(runner.active()).toBe(false) @@ -247,6 +242,40 @@ describe('SVG.Runner', function () { }) }) + describe('position()', function () { + it('get the position of a runner', function () { + var spy = jasmine.createSpy('stepper') + var runner = new SVG.Runner(1000).queue(null, spy) + + runner.step(300) + expect(spy).toHaveBeenCalledWith(0.3) + + expect(runner.position()).toBe(0.3) + }) + it('sets the position of the runner', function () { + var spy = jasmine.createSpy('stepper') + var runner = new SVG.Runner(1000).queue(null, spy) + + expect(runner.position(0.5).position()).toBe(0.5) + expect(spy).toHaveBeenCalledWith(0.5) + + expect(runner.position(0.1).position()).toBe(0.1) + expect(spy).toHaveBeenCalledWith(0.1) + + expect(runner.position(1.5).position()).toBe(1) + expect(spy).toHaveBeenCalledWith(1) + }) + it('sets the position of the runner in a loop', function () { + var spy = jasmine.createSpy('stepper') + var runner = new SVG.Runner(1000).loop(5, true, 500).queue(null, spy) + + expect(runner.position(1.3).position()).toBe(1.3) + expect(spy).toHaveBeenCalledWith(0.7) + + expect(runner.position(0.3).position()).toBe(0.3) + }) + }) + describe('element()', function () { it('returns the element bound to this runner if any', function () { var runner1 = new SVG.Runner() @@ -271,12 +300,12 @@ describe('SVG.Runner', function () { expect(runner.during(runFn)).toBe(runner) }) - it('calls queue giving only a function to call on every step', function () { + it('calls queue passing only a function to call on every step', function () { var runner = new SVG.Runner() spyOn(runner, 'queue') runner.during(runFn) - expect(runner.queue).toHaveBeenCalledWith(null, runFn, false) + expect(runner.queue).toHaveBeenCalledWith(null, runFn) }) }) @@ -286,7 +315,7 @@ describe('SVG.Runner', function () { expect(runner.finish()).toBe(runner) }) - it('calls step with Infinity as arument', function () { + it('calls step with Infinity as argument', function () { var runner = new SVG.Runner() spyOn(runner, 'step') runner.finish() @@ -301,12 +330,11 @@ describe('SVG.Runner', function () { expect(runner.reverse()).toBe(runner) }) - it('reverses the runner by setting the time to the end and going backwards', function () { - var runner = new SVG.Runner() - spyOn(runner, 'time') - runner.reverse() - - expect(runner.time).toHaveBeenCalledWith(SVG.defaults.timeline.duration) + it('reverses the runner', function () { + var spy = jasmine.createSpy('stepper') + var runner = new SVG.Runner(1000).reverse().queue(null, spy) + runner.step(750) + expect(spy).toHaveBeenCalledWith(0.25) }) }) diff --git a/src/morph.js b/src/morph.js index cf674bf..36ab2fa 100644 --- a/src/morph.js +++ b/src/morph.js @@ -235,8 +235,8 @@ SVG.extend(SVG.MorphableTypes, { .from(this.valueOf()) .to(val, args) }, - fromArray: function () { - this.constructor.apply(this, arguments) + fromArray: function (arr) { + this.constructor.call(this, arr) return this } }) @@ -352,8 +352,6 @@ fn () => { .after(fn) } - - When you start an element has a base matrix B - which starts as the identity If you modify the matrix, then we have: diff --git a/src/number.js b/src/number.js index 1a24954..7fef7f2 100644 --- a/src/number.js +++ b/src/number.js @@ -3,6 +3,7 @@ SVG.Number = SVG.invent({ // Initialize create: function (value, unit) { + unit = Array.isArray(value) ? value[1] : unit value = Array.isArray(value) ? value[0] : value // initialize defaults @@ -48,7 +49,7 @@ SVG.Number = SVG.invent({ return this.toString() }, // Convert to primitive toArray: function () { - return [this.value] + return [this.value, this.unit] }, valueOf: function () { return this.value diff --git a/src/runner.js b/src/runner.js index 1b0ce90..e77bcb3 100644 --- a/src/runner.js +++ b/src/runner.js @@ -6,9 +6,6 @@ SVG.easing = { '<': function (pos) { return -Math.cos(pos * Math.PI / 2) + 1 } } -// function sanitise - - SVG.Runner = SVG.invent({ inherit: SVG.EventTarget, @@ -168,13 +165,12 @@ SVG.Runner = SVG.invent({ return this }, - // FIXME: When not using queue the example is not working anymore during: function (fn) { - return this.on('during', fn, this) + return this.queue(null, fn) }, after (fn) { - return this.on('finish', fn, this) + return this.on('finish', fn) }, /* @@ -184,78 +180,89 @@ SVG.Runner = SVG.invent({ */ time: function (time) { - if (time == null) return this._time + if (time == null) { + return this._time + } let dt = time - this._time this.step(dt) return this }, - step: function (dt) { + duration: function () { + return this._times * (this._wait + this._duration) - this._wait + }, - // If there is no duration, we are in declarative mode and dt has to be - // positive always, so if its negative, we ignore it. - if (this._isDeclarative && dt < 0) return this - - // When no duration is set, all numbers including this._time end up NaN - // and that makes step returning at the first check - if(!this._isDeclarative) { - // If the user gives us a huge dt, figure out how many full loops - // have passed during this time. A full loop is the time required to - var absolute = this._time + dt + this._wait - var period = this._duration + this._wait - var nPeriods = Math.floor(absolute / period) - this._loopsDone += nPeriods - this._time = ((absolute % period) + period) % period - this._wait - - // FIXME: Without that it loops forever even without trying to loop - if(this._loopsDone >= this._times) this._time = Infinity - - // Make sure we reverse the code if we had an odd number of loops - this.reversed = (nPeriods % 2 === 0) ? this.reversed : !this.reversed + position: function (p) { + var loopDuration = this._duration + this._wait + if (p == null) { + var loopsDone = Math.floor(this._time / loopDuration) + var relativeTime = (this._time - loopsDone * loopDuration) + var position = relativeTime / this._duration + return Math.min(loopsDone + position, this._times) } + var whole = Math.floor(p) + var partial = p % 1 + var time = loopDuration * whole + this._duration * partial + return this.time(time) + }, - // Increment the time and read out the parameters - // this._time += dt - var duration = this._duration || Infinity - var time = this._time + absolute: function (p) { + if (p == null) { + return Math.min(1, this._time / this.duration()) + } + return this.time(p * this.duration()) + }, - // Work out if we are in range to run the function - var timeInside = 0 <= time && time <= duration - var finished = time >= duration - var position = finished ? 1 : time / duration + step: function (dt) { - // Deal with reversing - position = this._reversing ? 1 - position : position + // Update the time + dt = dt == null ? 16 : dt + this._time += dt + + // If we exceed the duration, just set the time to the duration + var duration = this.duration() + this._time = Math.min(duration, this._time) + + // Deal with non-declarative animations directly + // Explanation: https://www.desmos.com/calculator/wrnybkho4f + // Figure out how many loops we've done + var loopDuration = this._duration + this._wait + var loopsDone = Math.floor(this._time / loopDuration) + + // Figure out if we need to run the animation backwards + var swinging = this._swing && (loopsDone % 2 === 1) + var reversing = (swinging && !this._reversing) + || (!swinging && this._reversing) + + // Figure out the position + var position = this._time < duration + ? (this._time % loopDuration) / this._duration + : 1 + var clipPosition = Math.min(1, position) + var stepperPosition = reversing ? 1 - clipPosition : clipPosition + + // Figure out if we need to run + var runNow = this._lastPosition !== stepperPosition && this._time >= 0 + this._lastPosition = stepperPosition + + // Figure out if we just started + var justStarted = this._lastTime < 0 && this._time > 0 + var justFinished = this._lastTime < this._time && this.time > duration + this._lastTime = this._time + if (justStarted) { + this.fire('start', this) + } - // If we are on the rising edge, initialise everything, otherwise, - // initialise only what needs to be initialised on the rising edge - var justFinished = this._last <= duration && finished + // Call initialise and the run function this._initialise() - this._last = time - - // If we haven't started yet or we are over the time, just exit - if(!timeInside && !justFinished) return finished - - // Run the runner and store the last time it was run - var runnersFinished = this._run(this._isDeclarative ? dt : position) - finished = (this._isDeclarative && runnersFinished) - || (!this._isDeclarative && finished) - - // Set whether this runner is complete or not - this.done = finished - - // Deal with looping if we just finished an animation - if (this.done && ++this._loopsDone < this._times && !this._isDeclarative) { - - // If swinging, toggle the reversing flag - this._reversing = this._swing ? !this._reversing : this._reversing - - // Set the time to the wait time, and mark that we are not done yet - this._time = - this._wait - this.done = false + if ( runNow || this._isDeclarative ) { + var converged = this._run(this._isDeclarative ? dt : stepperPosition) + this.fire('step', this) } - // Fire finished event if finished + // Work out if we are done and return this + this.done = (converged && this._isDeclarative) + || (this._time >= duration && !justFinished && !this._isDeclarative) if (this.done) { this.fire('finish', this) } @@ -267,10 +274,7 @@ SVG.Runner = SVG.invent({ }, reverse: function (reverse) { - if (reverse === this._haveReversed) return this this._reversing = reverse == null ? !this._reversing : reverse - this._waitReverse = reverse == null ? !this._waitReverse : reverse - this._haveReversed = reverse == null ? this._haveReversed : null return this }, @@ -335,7 +339,8 @@ SVG.Runner = SVG.invent({ if(this._history[method]) { this._history[method].morpher.to(target) this._history[method].caller.finished = false - this.timeline()._continue() + var timeline = this.timeline() + timeline && timeline._continue() return true } return false diff --git a/src/timeline.js b/src/timeline.js index 864ca71..f3c1cdf 100644 --- a/src/timeline.js +++ b/src/timeline.js @@ -139,6 +139,7 @@ SVG.Timeline = SVG.invent({ seek (dt) { this._time += dt + this._continue() return this }, diff --git a/useCases.md b/useCases.md index 328ca2a..d6acdcb 100644 --- a/useCases.md +++ b/useCases.md @@ -325,7 +325,6 @@ The user might want to react to some events that the runner might emit. We will emit the following events from the runner: - start - when a runner first initialises - finish - when a runner finishes -- during - on every step -- done - when a function completes +- step - on every step Maybe they also want to react to timeline events as well |