-
// for (let i = 0 ; i < 15; i++) {
// for (let j = 0 ; j < 10; j++) {
//
// }
// }
-var randPoint = (x = 50, y = 50) => [
- Math.random() * 100 - 50 + x,
- Math.random() * 100 - 50 + y
-]
-
-var poly = SVG('<polygon>').plot([
- randPoint(),
- randPoint(),
- randPoint(),
- randPoint(),
- randPoint()
-]).attr({fill: 'none', stroke: 'black'}).addTo('svg')
-var polyAni = poly.animate(new SVG.Spring(300, 50))
-
-SVG.on(document, 'click', function (e) {
- polyAni.plot([
- randPoint(e.pageX-50, e.pageY-50),
- randPoint(e.pageX+50, e.pageY-50),
- randPoint(e.pageX+50, e.pageY),
- randPoint(e.pageX+50, e.pageY+50),
- randPoint(e.pageX-50, e.pageY+50)
- ])
-})
+// var bla = SVG('<rect>').size(50, 50).center(100, 100).addTo('svg')
+// bla.animate().move(220, 200)
-// var mover = SVG('<ellipse>').size(50, 50).center(100, 100).addTo('svg')
-// var anim = mover.animate(SVG.PID(null, null, null, false)).move(500, 500)
+// var randPoint = (x = 50, y = 50) => [
+// Math.random() * 100 - 50 + x,
+// Math.random() * 100 - 50 + y
+// ]
+//
+// var poly = SVG('<polygon>').plot([
+// randPoint(),
+// randPoint(),
+// randPoint(),
+// randPoint(),
+// randPoint()
+// ]).attr({fill: 'none', stroke: 'black'}).addTo('svg')
+// var polyAni = poly.animate(new SVG.Spring(3000, 0))
//
-// SVG.on(document, 'mousemove', function (e) {
-// //mover.animate(SVG.PID()).move(e.pageX, e.pageY)
-// var p = mover.point(e.pageX, e.pageY)
-// anim.center(p.x, p.y)
+// SVG.on(document, 'click', function (e) {
+// polyAni.plot([
+// randPoint(e.pageX-50, e.pageY-50),
+// randPoint(e.pageX+50, e.pageY-50),
+// randPoint(e.pageX+50, e.pageY),
+// randPoint(e.pageX+50, e.pageY+50),
+// randPoint(e.pageX-50, e.pageY+50)
+// ])
// })
+var mover = SVG('<ellipse>').size(50, 50).center(100, 100).addTo('svg')
+var anim = mover.animate(new SVG.Spring(500, 10)).move(500, 500)
+
+SVG.on(document, 'mousemove', function (e) {
+ //mover.animate(SVG.PID()).move(e.pageX, e.pageY)
+ var p = mover.point(e.pageX, e.pageY)
+ anim.center(p.x, p.y)
+})
+
</script>
</body>
},
})
-
function recalculate () {
// Apply the default parameters
- this._duration = this._duration || 500
- this._overshoot = this._overshoot || 0
+ var duration = (this._duration || 500) / 1000
+ var overshoot = this._overshoot || 0
// Calculate the PID natural response
var eps = 1e-10
- var os = this._overshoot / 100 + eps
- var zeta = -Math.log(os) / Math.sqrt(Math.PI ** 2 + Math.log(os) ** 2)
- var wn = 4 / (zeta * this._duration / 1000)
+ var pi = Math.PI
+ var os = Math.log(overshoot / 100 + eps)
+ var zeta = - os / Math.sqrt(pi * pi + os * os)
+ var wn = 3.9 / ( zeta * duration )
// Calculate the Spring values
this.d = 2 * zeta * wn
if(dt == 0) return current
dt /= 1000
- // Get the parameters
- var error = target - current
- var lastError = c.error || 0
- var velocity = (error - lastError) / dt
+ // Get the previous velocity
+ var velocity = c.velocity || 0
// Apply the control to get the new position and store it
- var control = this.d * velocity + this.k * error
- var newPosition = current + 2 * control * dt * dt / 2
+ var acceleration = - this.d * velocity - this.k * (current - target)
+ var newPosition = current
+ + velocity * dt
+ + acceleration * dt * dt / 2
+
+ // Store the velocity
+ c.velocity = velocity + acceleration * dt
- c.error = error
- c.done = false //Math.abs(error) < 0.001
- return newPosition
+ // Figure out if we have converged, and if so, pass the value
+ c.done = Math.abs(target - newPosition) + Math.abs(velocity) < 0.002
+ return c.done ? target : newPosition
},
duration: makeSetterGetter('_duration', recalculate),
c.done = Math.abs(p) < 0.001
- return current + (this.P * p + this.I * i + this.D * d)
+ return c.done ? target : current + (this.P * p + this.I * i + this.D * d)
},
windup: makeSetterGetter('windup'),
},
// Unbind event from listener
off: function (event, listener) {
- SVG.off(this.node, event, listener)
+ SVG.off(this, event, listener)
return this
},
dispatch: function (event, data) {
: options
// Declare all of the variables
+ this._dispacher = document.createElement('div')
this._element = null
- this._queue = []
this.done = false
+ this._queue = []
// Work out the stepper and the duration
this._duration = typeof options === 'number' && options
alwaysInitialise: alwaysInitialise || false,
initialiser: initFn || SVG.void,
runner: runFn || SVG.void,
+ initialised: false,
finished: false,
})
this.timeline()._continue()
- this._element.timeline()._continue()
return this
},
return this.queue(null, runFn, false)
},
+ on (eventName, fn) {
+ SVG.on(this._dispacher, eventName, fn, this)
+ return this
+ },
+
+ // Queue a function to run after this runner
+ after (time, fn) {
+ return this.on('finish', fn)
+ },
+
+ fire: function (name) {
+
+ }
+
/*
Runner animation methods
========================
// If we are on the rising edge, initialise everything, otherwise,
// initialise only what needs to be initialised on the rising edge
- var justStarted = this._last <= 0 && time >= 0
+ // var justStarted = this._last <= 0 && time >= 0
var justFinished = this._last <= duration && finished
- this._initialise(justStarted)
+ this._initialise()
this._last = time
// If we haven't started yet or we are over the time, just exit
// Set whether this runner is complete or not
this.done = finished
+ if (this.done) {
+ this._afterEvents.forEach(function (event) { event(this) })
+
+ if (this._element) this._element.fire(`runner.${id}.finish`, {runner: this})
+
+ el.animate().after()
+ el.on()
+ }
return this
},
},
// Run each initialise function in the runner if required
- _initialise: function (all) {
+ _initialise: function () {
for (var i = 0, len = this._queue.length; i < len ; ++i) {
// Get the current initialiser
var current = this._queue[i]
// Determine whether we need to initialise
- var always = current.alwaysInitialise
+ var needsInit = current.alwaysInitialise || !current.initialised
var running = !current.finished
- if ((always || all) && running) {
+
+ if (needsInit && running) {
current.initialiser.call(this._element)
+ current.initialised = true
}
}
},
'<': function (pos) { return -Math.cos(pos * Math.PI / 2) + 1 }
}
-var time = window.performance || window.Date
-
SVG.Timeline = SVG.invent({
// Construct a new timeline on the given element
extend: {
- /**
- * Runner Constructors
- */
-
- animate (duration, delay, nowOrAbsolute) {
-
- // Clear the controller and the looping parameters
- this._controller = duration instanceof Function ? duration : null
- this._backwards = false
- this._swing = false
- this._loops = 0
-
- // If we have an object we are declaring imperative animations
- if (typeof duration === 'object') {
- duration = duration.duration
- delay = duration.delay
- nowOrAbsolute = duration.absolute || duration.now
- }
-
- // The start time for the next animation can either be given explicitly,
- // derived from the current timeline time or it can be relative to the
- // last start time to chain animations direclty
- var absoluteStartTime = typeof nowOrAbsolute === 'number' ? nowOrAbsolute
- : nowOrAbsolute ? this._time
- : this._startTime + this._duration
-
- // We start the next animation after the delay required
- this._startTime = absoluteStartTime + (delay || 0)
- this._duration = duration instanceof Function ? null
- : (duration || SVG.defaults.timeline.duration)
-
- // Make a new runner to queue all of the animations onto
- this._runner = new Runner(this._time - this._startTime, this.duration)
- this._runners.push(this._runner)
-
- // Step the animation
- this._step()
-
- // Allow for chaining
- return this
- },
-
- delay (by, now) {
- return this.animate(0, by, now)
- },
-
- /**
- * Runner Behaviours
- */
-
- loop (swing, times, wait) {
-
- },
-
- ease (fn) {
- var ease = SVG.easing[fn || SVG.defaults.timeline.ease] || fn
- this._controller = function (from, to, pos) {
- // FIXME: This is needed for at lest ObjectBag but could slow down stuff
- if(typeof from !== 'number') {
- return pos < 1 ? from : to
- }
- return from + (to - from) * ease(pos)
- }
- return this
- },
+ // /**
+ // * Runner Constructors
+ // */
+ //
+ // animate (duration, delay, nowOrAbsolute) {
+ //
+ // // Clear the controller and the looping parameters
+ // this._controller = duration instanceof Function ? duration : null
+ // this._backwards = false
+ // this._swing = false
+ // this._loops = 0
+ //
+ // // If we have an object we are declaring imperative animations
+ // if (typeof duration === 'object') {
+ // duration = duration.duration
+ // delay = duration.delay
+ // nowOrAbsolute = duration.absolute || duration.now
+ // }
+ //
+ // // The start time for the next animation can either be given explicitly,
+ // // derived from the current timeline time or it can be relative to the
+ // // last start time to chain animations direclty
+ // var absoluteStartTime = typeof nowOrAbsolute === 'number' ? nowOrAbsolute
+ // : nowOrAbsolute ? this._time
+ // : this._startTime + this._duration
+ //
+ // // We start the next animation after the delay required
+ // this._startTime = absoluteStartTime + (delay || 0)
+ // this._duration = duration instanceof Function ? null
+ // : (duration || SVG.defaults.timeline.duration)
+ //
+ // // Make a new runner to queue all of the animations onto
+ // this._runner = new Runner(this._time - this._startTime, this.duration)
+ // this._runners.push(this._runner)
+ //
+ // // Step the animation
+ // this._continue()
+ //
+ // // Allow for chaining
+ // return this
+ // },
+ //
+ // delay (by, now) {
+ // return this.animate(0, by, now)
+ // },
+ //
+ // /**
+ // * Runner Behaviours
+ // */
+ //
+ // loop (swing, times, wait) {
+ //
+ // },
+ //
+ // ease (fn) {
+ // var ease = SVG.easing[fn || SVG.defaults.timeline.ease] || fn
+ // this._controller = function (from, to, pos) {
+ // // FIXME: This is needed for at lest ObjectBag but could slow down stuff
+ // if(typeof from !== 'number') {
+ // return pos < 1 ? from : to
+ // }
+ // return from + (to - from) * ease(pos)
+ // }
+ // return this
+ // },
reverse () {
*
*/
- tag (name) {
- this._runner.tag(name)
- },
-
- runner (tag) {
- if (tag) {
- return this._runners.find(function (runTag) {return runTag === tag})
- } else {
- return this._runner
- }
- },
+ // tag (name) {
+ // this._runner.tag(name)
+ // },
+ //
+ // runner (tag) {
+ // if (tag) {
+ // return this._runners.find(function (runTag) {return runTag === tag})
+ // } else {
+ // return this._runner
+ // }
+ // },
schedule (runner, delay, when) {
delay = delay || 0
// Work out when to start the animation
- if ( when == null || when === 'last' || when === 'relative' ) {
+ if ( when == null || when === 'last' || when === 'after' ) {
// Take the last time and increment
- // FIXME: How to figue out the relative time? Maybe use runner.endTime()
absoluteStartTime = this._startTime + delay
} else if (when === 'absolute' || when === 'start' ) {
absoluteStartTime = delay
+
} else if (when === 'now') {
absoluteStartTime = this._time + delay
+
+ } else if ( when === 'relative' ) {
+
+ // TODO: If the runner already exists, shift it by the delay, otherwise
+ // this is relative to the start time ie: 0
+
} else {
// TODO: Throw error
}
runner.time(-absoluteStartTime)
this._startTime = absoluteStartTime + runner._duration
this._runners.push(runner)
-
- this._step()
-
+ this._continue()
return this
},
// 0 by default
},
- queue (initFn, runFn) {
-
- // Make sure there is a function available
- initFn = (initFn || SVG.void).bind(this)
- runFn = (runFn || SVG.void).bind(this)
-
- // Add the functions to the active runner
- this._runner.add(initFn, runFn)
- return this
- },
+ // queue (initFn, runFn) {
+ //
+ // // Make sure there is a function available
+ // initFn = (initFn || SVG.void).bind(this)
+ // runFn = (runFn || SVG.void).bind(this)
+ //
+ // // Add the functions to the active runner
+ // this._runner.add(initFn, runFn)
+ // return this
+ // },
// Queue a function to run after some time
after (time, fn) {
.tag('second')
.scale(3)
-animation.finish('first')
-animation.pause('first')
-animation.stop('first')
-animation.play('first')
+element.timeline.finish()
+element.timeline.pause()
+element.timeline.stop()
+element.timeline.play()
```
```js
-var animation = element
- .animate(2000).move(200, 200)
+var animation = element.animate(2000).move(200, 200)
// after 1000 ms
-animation.animate(1000, 0, 500).scale(2)
+animation.animate(1000, 500, 'absolute').scale(2)
+
+
+var runner = elemenet.move(0, 0).animate(1000)
+
+// after 500ms
+runner.move(200, 200)
```
```js
-let animationA = element.loop(300, ">").rotate(360).runner()
-let animationB = element
- .loop(200, "><")
- .scale(2)
- .runner(tag)
+let animationA = element.loop(300, ">").rotate(360)
+let animationB = element.loop(200, "><").scale(2)
-// After some time, they might want to end the first animation abruptly
-animationB.enable(false).end()
-
-// Maybe they want to pause a runner
-animationB.enable(false)
+// Maybe they want to disable a runner - which acts like pausing
+animationB.active(false)
// Maybe they want to remove an animation matching a tag
animationB.tag('B')
// They can move around a runner as well
element.timeline()
- .schedule('B', 300) // Moves a runner to start at 300
+ .schedule('B', 300, 'absolute') // Moves a runner to start at 300
// time(currentAbsolute - newAbsolute)
.shift('B', 300) // Shifts the runner start time by 300
+ // which is sugar to
+ .schedule('B', 300, 'relative')
// seek(shiftTime)
```
+Lets demonstrate the difference between the schedule and shift
+
+```
+Given this:
+
+ --------
+ --------------
+ ----------------
+
+Schedule:
+ --------
+ --------------
+ ----------------
+
+Shift:
+ --------
+ --------------
+ ----------------
+```
+
+
# A Sequenced Animation
.animate()...
```
+
+# Binding Events
+
+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
+
+Maybe they also want to react to timeline events as well