summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--dirty.html92
-rw-r--r--spec/spec/runner.js112
-rw-r--r--src/morph.js6
-rw-r--r--src/number.js3
-rw-r--r--src/runner.js141
-rw-r--r--src/timeline.js1
-rw-r--r--useCases.md3
7 files changed, 215 insertions, 143 deletions
diff --git a/dirty.html b/dirty.html
index 8b87e11..dc92ae8 100644
--- a/dirty.html
+++ b/dirty.html
@@ -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