From: Saivan Date: Mon, 28 May 2018 13:46:02 +0000 (+1000) Subject: Started planning the way events work and got Spring working X-Git-Tag: 3.0.0~60^2~57 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=be8320d4bfada1f5fc1c7eaef8402c52f9d9be86;p=svg.js.git Started planning the way events work and got Spring working --- diff --git a/dirty.html b/dirty.html index 1b8a3e3..41c1583 100644 --- a/dirty.html +++ b/dirty.html @@ -54,7 +54,6 @@ function getColor(t) { - // for (let i = 0 ; i < 15; i++) { // for (let j = 0 ; j < 10; j++) { // @@ -75,39 +74,42 @@ function getColor(t) { // } // } -var randPoint = (x = 50, y = 50) => [ - Math.random() * 100 - 50 + x, - Math.random() * 100 - 50 + y -] - -var poly = SVG('').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('').size(50, 50).center(100, 100).addTo('svg') +// bla.animate().move(220, 200) -// var mover = SVG('').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('').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('').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) +}) + diff --git a/src/controller.js b/src/controller.js index 4ed0ac5..359dc9f 100644 --- a/src/controller.js +++ b/src/controller.js @@ -91,18 +91,18 @@ SVG.Controller = SVG.invent ({ }, }) - 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 @@ -125,18 +125,21 @@ SVG.Spring = SVG.invent ({ 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), @@ -181,7 +184,7 @@ SVG.PID = SVG.invent ({ 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'), diff --git a/src/event.js b/src/event.js index 9d96ea7..ece00a8 100644 --- a/src/event.js +++ b/src/event.js @@ -127,7 +127,7 @@ SVG.extend(SVG.Element, { }, // 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) { diff --git a/src/runner.js b/src/runner.js index eecb950..68dd7e8 100644 --- a/src/runner.js +++ b/src/runner.js @@ -23,9 +23,10 @@ SVG.Runner = SVG.invent({ : 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 @@ -146,10 +147,10 @@ SVG.Runner = SVG.invent({ alwaysInitialise: alwaysInitialise || false, initialiser: initFn || SVG.void, runner: runFn || SVG.void, + initialised: false, finished: false, }) this.timeline()._continue() - this._element.timeline()._continue() return this }, @@ -157,6 +158,20 @@ SVG.Runner = SVG.invent({ 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 ======================== @@ -189,9 +204,9 @@ SVG.Runner = SVG.invent({ // 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 @@ -208,6 +223,14 @@ SVG.Runner = SVG.invent({ // 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 }, @@ -287,16 +310,18 @@ SVG.Runner = SVG.invent({ }, // 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 } } }, diff --git a/src/timeline.js b/src/timeline.js index be8d52e..154dabe 100644 --- a/src/timeline.js +++ b/src/timeline.js @@ -7,8 +7,6 @@ SVG.easing = { '<': 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 @@ -40,71 +38,71 @@ SVG.Timeline = SVG.invent({ 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 () { @@ -114,17 +112,17 @@ SVG.Timeline = SVG.invent({ * */ - 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) { @@ -135,15 +133,21 @@ SVG.Timeline = SVG.invent({ 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 } @@ -151,9 +155,7 @@ SVG.Timeline = SVG.invent({ runner.time(-absoluteStartTime) this._startTime = absoluteStartTime + runner._duration this._runners.push(runner) - - this._step() - + this._continue() return this }, @@ -202,16 +204,16 @@ SVG.Timeline = SVG.invent({ // 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) { diff --git a/useCases.md b/useCases.md index a15d511..328ca2a 100644 --- a/useCases.md +++ b/useCases.md @@ -15,10 +15,10 @@ var animation = element .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() ``` @@ -30,11 +30,16 @@ The user can specify their time which is relative to the timelines time. ```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) ``` @@ -51,17 +56,11 @@ control over each animation that they define. ```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') @@ -69,13 +68,36 @@ element.timeline().remove('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 @@ -296,3 +318,14 @@ runner.schedule(timeline, ...rest) .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