summaryrefslogtreecommitdiffstats
path: root/src/runner.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/runner.js')
-rw-r--r--src/runner.js928
1 files changed, 0 insertions, 928 deletions
diff --git a/src/runner.js b/src/runner.js
deleted file mode 100644
index 97e04e2..0000000
--- a/src/runner.js
+++ /dev/null
@@ -1,928 +0,0 @@
-/* global isMatrixLike getOrigin */
-
-SVG.easing = {
- '-': function (pos) { return pos },
- '<>': function (pos) { return -Math.cos(pos * Math.PI) / 2 + 0.5 },
- '>': function (pos) { return Math.sin(pos * Math.PI / 2) },
- '<': function (pos) { return -Math.cos(pos * Math.PI / 2) + 1 }
-}
-
-SVG.Runner = SVG.invent({
- parent: SVG.Element,
-
- create: function (options) {
- // Store a unique id on the runner, so that we can identify it later
- this.id = SVG.Runner.id++
-
- // Ensure a default value
- options = options == null
- ? SVG.defaults.timeline.duration
- : options
-
- // Ensure that we get a controller
- options = typeof options === 'function'
- ? new SVG.Controller(options)
- : options
-
- // Declare all of the variables
- this._element = null
- this._timeline = null
- this.done = false
- this._queue = []
-
- // Work out the stepper and the duration
- this._duration = typeof options === 'number' && options
- this._isDeclarative = options instanceof SVG.Controller
- this._stepper = this._isDeclarative ? options : new SVG.Ease()
-
- // We copy the current values from the timeline because they can change
- this._history = {}
-
- // Store the state of the runner
- this.enabled = true
- this._time = 0
- this._last = 0
-
- // Save transforms applied to this runner
- this.transforms = new SVG.Matrix()
- this.transformId = 1
-
- // Looping variables
- this._haveReversed = false
- this._reverse = false
- this._loopsDone = 0
- this._swing = false
- this._wait = 0
- this._times = 1
- },
-
- construct: {
-
- animate: function (duration, delay, when) {
- var o = SVG.Runner.sanitise(duration, delay, when)
- var timeline = this.timeline()
- return new SVG.Runner(o.duration)
- .loop(o)
- .element(this)
- .timeline(timeline)
- .schedule(delay, when)
- },
-
- delay: function (by, when) {
- return this.animate(0, by, when)
- }
- },
-
- extend: {
-
- /*
- Runner Definitions
- ==================
- These methods help us define the runtime behaviour of the Runner or they
- help us make new runners from the current runner
- */
-
- element: function (element) {
- if (element == null) return this._element
- this._element = element
- element._prepareRunner()
- return this
- },
-
- timeline: function (timeline) {
- // check explicitly for undefined so we can set the timeline to null
- if (typeof timeline === 'undefined') return this._timeline
- this._timeline = timeline
- return this
- },
-
- animate: function (duration, delay, when) {
- var o = SVG.Runner.sanitise(duration, delay, when)
- var runner = new SVG.Runner(o.duration)
- if (this._timeline) runner.timeline(this._timeline)
- if (this._element) runner.element(this._element)
- return runner.loop(o).schedule(delay, when)
- },
-
- schedule: function (timeline, delay, when) {
- // The user doesn't need to pass a timeline if we already have one
- if (!(timeline instanceof SVG.Timeline)) {
- when = delay
- delay = timeline
- timeline = this.timeline()
- }
-
- // If there is no timeline, yell at the user...
- if (!timeline) {
- throw Error('Runner cannot be scheduled without timeline')
- }
-
- // Schedule the runner on the timeline provided
- timeline.schedule(this, delay, when)
- return this
- },
-
- unschedule: function () {
- var timeline = this.timeline()
- timeline && timeline.unschedule(this)
- return this
- },
-
- loop: function (times, swing, wait) {
- // Deal with the user passing in an object
- if (typeof times === 'object') {
- swing = times.swing
- wait = times.wait
- times = times.times
- }
-
- // Sanitise the values and store them
- this._times = times || Infinity
- this._swing = swing || false
- this._wait = wait || 0
- return this
- },
-
- delay: function (delay) {
- return this.animate(0, delay)
- },
-
- /*
- Basic Functionality
- ===================
- These methods allow us to attach basic functions to the runner directly
- */
-
- queue: function (initFn, runFn, isTransform) {
- this._queue.push({
- initialiser: initFn || SVG.void,
- runner: runFn || SVG.void,
- isTransform: isTransform,
- initialised: false,
- finished: false
- })
- var timeline = this.timeline()
- timeline && this.timeline()._continue()
- return this
- },
-
- during: function (fn) {
- return this.queue(null, fn)
- },
-
- after (fn) {
- return this.on('finish', fn)
- },
-
- /*
- Runner animation methods
- ========================
- Control how the animation plays
- */
-
- time: function (time) {
- if (time == null) {
- return this._time
- }
- let dt = time - this._time
- this.step(dt)
- return this
- },
-
- duration: function () {
- return this._times * (this._wait + this._duration) - this._wait
- },
-
- loops: 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)
- },
-
- position: function (p) {
- // Get all of the variables we need
- var x = this._time
- var d = this._duration
- var w = this._wait
- var t = this._times
- var s = this._swing
- var r = this._reverse
- var position
-
- if (p == null) {
- /*
- This function converts a time to a position in the range [0, 1]
- The full explanation can be found in this desmos demonstration
- https://www.desmos.com/calculator/u4fbavgche
- The logic is slightly simplified here because we can use booleans
- */
-
- // Figure out the value without thinking about the start or end time
- const f = function (x) {
- var swinging = s * Math.floor(x % (2 * (w + d)) / (w + d))
- var backwards = (swinging && !r) || (!swinging && r)
- var uncliped = Math.pow(-1, backwards) * (x % (w + d)) / d + backwards
- var clipped = Math.max(Math.min(uncliped, 1), 0)
- return clipped
- }
-
- // Figure out the value by incorporating the start time
- var endTime = t * (w + d) - w
- position = x <= 0 ? Math.round(f(1e-5))
- : x < endTime ? f(x)
- : Math.round(f(endTime - 1e-5))
- return position
- }
-
- // Work out the loops done and add the position to the loops done
- var loopsDone = Math.floor(this.loops())
- var swingForward = s && (loopsDone % 2 === 0)
- var forwards = (swingForward && !r) || (r && swingForward)
- position = loopsDone + (forwards ? p : 1 - p)
- return this.loops(position)
- },
-
- progress: function (p) {
- if (p == null) {
- return Math.min(1, this._time / this.duration())
- }
- return this.time(p * this.duration())
- },
-
- step: function (dt) {
- // If we are inactive, this stepper just gets skipped
- if (!this.enabled) return this
-
- // Update the time and get the new position
- dt = dt == null ? 16 : dt
- this._time += dt
- var position = this.position()
-
- // Figure out if we need to run the stepper in this frame
- var running = this._lastPosition !== position && this._time >= 0
- this._lastPosition = position
-
- // Figure out if we just started
- var duration = this.duration()
- 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)
- }
-
- // Work out if the runner is finished set the done flag here so animations
- // know, that they are running in the last step (this is good for
- // transformations which can be merged)
- var declarative = this._isDeclarative
- this.done = !declarative && !justFinished && this._time >= duration
-
- // Call initialise and the run function
- if (running || declarative) {
- this._initialise(running)
-
- // clear the transforms on this runner so they dont get added again and again
- this.transforms = new SVG.Matrix()
- var converged = this._run(declarative ? dt : position)
- // this.fire('step', this)
- }
- // correct the done flag here
- // declaritive animations itself know when they converged
- this.done = this.done || (converged && declarative)
- // if (this.done) {
- // this.fire('finish', this)
- // }
- return this
- },
-
- finish: function () {
- return this.step(Infinity)
- },
-
- reverse: function (reverse) {
- this._reverse = reverse == null ? !this._reverse : reverse
- return this
- },
-
- ease: function (fn) {
- this._stepper = new SVG.Ease(fn)
- return this
- },
-
- active: function (enabled) {
- if (enabled == null) return this.enabled
- this.enabled = enabled
- return this
- },
-
- /*
- Private Methods
- ===============
- Methods that shouldn't be used externally
- */
-
- // Save a morpher to the morpher list so that we can retarget it later
- _rememberMorpher: function (method, morpher) {
- this._history[method] = {
- morpher: morpher,
- caller: this._queue[this._queue.length - 1]
- }
- },
-
- // Try to set the target for a morpher if the morpher exists, otherwise
- // do nothing and return false
- _tryRetarget: function (method, target) {
- if (this._history[method]) {
- // if the last method wasnt even initialised, throw it away
- if (!this._history[method].caller.initialised) {
- let index = this._queue.indexOf(this._history[method].caller)
- this._queue.splice(index, 1)
- return false
- }
-
- // for the case of transformations, we use the special retarget function
- // which has access to the outer scope
- if (this._history[method].caller.isTransform) {
- this._history[method].caller.isTransform(target)
- // for everything else a simple morpher change is sufficient
- } else {
- this._history[method].morpher.to(target)
- }
-
- this._history[method].caller.finished = false
- var timeline = this.timeline()
- timeline && timeline._continue()
- return true
- }
- return false
- },
-
- // Run each initialise function in the runner if required
- _initialise: function (running) {
- // If we aren't running, we shouldn't initialise when not declarative
- if (!running && !this._isDeclarative) return
-
- // Loop through all of the initialisers
- 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 needsIt = this._isDeclarative || (!current.initialised && running)
- running = !current.finished
-
- // Call the initialiser if we need to
- if (needsIt && running) {
- current.initialiser.call(this)
- current.initialised = true
- }
- }
- },
-
- // Run each run function for the position or dt given
- _run: function (positionOrDt) {
- // Run all of the _queue directly
- var allfinished = true
- for (var i = 0, len = this._queue.length; i < len; ++i) {
- // Get the current function to run
- var current = this._queue[i]
-
- // Run the function if its not finished, we keep track of the finished
- // flag for the sake of declarative _queue
- var converged = current.runner.call(this, positionOrDt)
- current.finished = current.finished || (converged === true)
- allfinished = allfinished && current.finished
- }
-
- // We report when all of the constructors are finished
- return allfinished
- },
-
- addTransform: function (transform, index) {
- this.transforms.lmultiplyO(transform)
- return this
- },
-
- clearTransform: function () {
- this.transforms = new SVG.Matrix()
- return this
- }
- }
-})
-
-SVG.Runner.id = 0
-
-SVG.Runner.sanitise = function (duration, delay, when) {
- // Initialise the default parameters
- var times = 1
- var swing = false
- var wait = 0
- duration = duration || SVG.defaults.timeline.duration
- delay = delay || SVG.defaults.timeline.delay
- when = when || 'last'
-
- // If we have an object, unpack the values
- if (typeof duration === 'object' && !(duration instanceof SVG.Stepper)) {
- delay = duration.delay || delay
- when = duration.when || when
- swing = duration.swing || swing
- times = duration.times || times
- wait = duration.wait || wait
- duration = duration.duration || SVG.defaults.timeline.duration
- }
-
- return {
- duration: duration,
- delay: delay,
- swing: swing,
- times: times,
- wait: wait,
- when: when
- }
-}
-
-SVG.FakeRunner = class {
- constructor (transforms = new SVG.Matrix(), id = -1, done = true) {
- this.transforms = transforms
- this.id = id
- this.done = done
- }
-}
-
-SVG.extend([SVG.Runner, SVG.FakeRunner], {
- mergeWith (runner) {
- return new SVG.FakeRunner(
- runner.transforms.lmultiply(this.transforms),
- runner.id
- )
- }
-})
-
-// SVG.FakeRunner.emptyRunner = new SVG.FakeRunner()
-
-const lmultiply = (last, curr) => last.lmultiplyO(curr)
-const getRunnerTransform = (runner) => runner.transforms
-
-function mergeTransforms () {
- // Find the matrix to apply to the element and apply it
- let runners = this._transformationRunners.runners
- let netTransform = runners
- .map(getRunnerTransform)
- .reduce(lmultiply, new SVG.Matrix())
-
- this.transform(netTransform)
-
- this._transformationRunners.merge()
-
- if (this._transformationRunners.length() === 1) {
- this._frameId = null
- }
-}
-
-class RunnerArray {
- constructor () {
- this.runners = []
- this.ids = []
- }
-
- add (runner) {
- if (this.runners.includes(runner)) return
-
- let id = runner.id + 1
-
- let leftSibling = this.ids.reduce((last, curr) => {
- if (curr > last && curr < id) return curr
- return last
- }, 0)
-
- let index = this.ids.indexOf(leftSibling) + 1
-
- this.ids.splice(index, 0, id)
- this.runners.splice(index, 0, runner)
-
- return this
- }
-
- getByID (id) {
- return this.runners[this.ids.indexOf(id + 1)]
- }
-
- remove (id) {
- let index = this.ids.indexOf(id + 1)
- this.ids.splice(index, 1)
- this.runners.splice(index, 1)
- return this
- }
-
- merge () {
- let lastRunner = null
- this.runners.forEach((runner, i) => {
- if (lastRunner && runner.done && lastRunner.done) {
- this.remove(runner.id)
- this.edit(lastRunner.id, runner.mergeWith(lastRunner))
- }
-
- lastRunner = runner
- })
-
- return this
- }
-
- edit (id, newRunner) {
- let index = this.ids.indexOf(id + 1)
- this.ids.splice(index, 1, id)
- this.runners.splice(index, 1, newRunner)
- return this
- }
-
- length () {
- return this.ids.length
- }
-
- clearBefore (id) {
- let deleteCnt = this.ids.indexOf(id + 1) || 1
- this.ids.splice(0, deleteCnt, 0)
- this.runners.splice(0, deleteCnt, new SVG.FakeRunner())
- return this
- }
-}
-
-SVG.extend(SVG.Element, {
- // this function searches for all runners on the element and deletes the ones
- // which run before the current one. This is because absolute transformations
- // overwfrite anything anyway so there is no need to waste time computing
- // other runners
- _clearTransformRunnersBefore: function (currentRunner) {
- this._transformationRunners.clearBefore(currentRunner.id)
- },
-
- _currentTransform (current) {
- return this._transformationRunners.runners
- // we need the equal sign here to make sure, that also transformations
- // on the same runner which execute before the current transformation are
- // taken into account
- .filter((runner) => runner.id <= current.id)
- .map(getRunnerTransform)
- .reduce(lmultiply, new SVG.Matrix())
- },
-
- addRunner: function (runner) {
- this._transformationRunners.add(runner)
-
- SVG.Animator.transform_frame(
- mergeTransforms.bind(this), this._frameId
- )
- },
-
- _prepareRunner: function () {
- if (this._frameId == null) {
- this._transformationRunners = new RunnerArray()
- .add(new SVG.FakeRunner(new SVG.Matrix(this)))
-
- this._frameId = SVG.Element.frameId++
- }
- }
-})
-
-SVG.Element.frameId = 0
-
-SVG.extend(SVG.Runner, {
- attr: function (a, v) {
- return this.styleAttr('attr', a, v)
- },
-
- // Add animatable styles
- css: function (s, v) {
- return this.styleAttr('css', s, v)
- },
-
- styleAttr (type, name, val) {
- // apply attributes individually
- if (typeof name === 'object') {
- for (var key in val) {
- this.styleAttr(type, key, val[key])
- }
- }
-
- var morpher = new SVG.Morphable(this._stepper).to(val)
-
- this.queue(function () {
- morpher = morpher.from(this.element()[type](name))
- }, function (pos) {
- this.element()[type](name, morpher.at(pos))
- return morpher.done()
- })
-
- return this
- },
-
- zoom: function (level, point) {
- var morpher = new SVG.Morphable(this._stepper).to(new SVG.Number(level))
-
- this.queue(function () {
- morpher = morpher.from(this.zoom())
- }, function (pos) {
- this.element().zoom(morpher.at(pos), point)
- return morpher.done()
- })
-
- return this
- },
-
- /**
- ** absolute transformations
- **/
-
- //
- // M v -----|-----(D M v = F 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 F(pos) = m + pos * (t - m)
- // - Note F(0) = M
- // - Note F(1) = T
- // 4. Now you get the delta matrix as a result: D = F * inv(M)
-
- transform: function (transforms, relative, affine) {
- // If we have a declarative function, we should retarget it if possible
- relative = transforms.relative || relative
- if (this._isDeclarative && !relative && this._tryRetarget('transform', transforms)) {
- return this
- }
-
- // Parse the parameters
- var isMatrix = isMatrixLike(transforms)
- affine = transforms.affine != null
- ? transforms.affine
- : (affine != null ? affine : !isMatrix)
-
- // Create a morepher and set its type
- const morpher = new SVG.Morphable()
- .type(affine ? SVG.Morphable.TransformBag : SVG.Matrix)
- .stepper(this._stepper)
-
- let origin
- let element
- let current
- let currentAngle
- let startTransform
-
- function setup () {
- // make sure element and origin is defined
- element = element || this.element()
- origin = origin || getOrigin(transforms, element)
-
- startTransform = new SVG.Matrix(relative ? undefined : element)
-
- // add the runner to the element so it can merge transformations
- element.addRunner(this)
-
- // Deactivate all transforms that have run so far if we are absolute
- if (!relative) {
- element._clearTransformRunnersBefore(this)
- }
- }
-
- function run (pos) {
- // clear all other transforms before this in case something is saved
- // on this runner. We are absolute. We dont need these!
- if (!relative) this.clearTransform()
-
- let {x, y} = new SVG.Point(origin).transform(element._currentTransform(this))
-
- let target = new SVG.Matrix({...transforms, origin: [x, y]})
- let start = this._isDeclarative && current
- ? current
- : startTransform
-
- if (affine) {
- target = target.decompose(x, y)
- start = start.decompose(x, y)
-
- // Get the current and target angle as it was set
- const rTarget = target.rotate
- const rCurrent = start.rotate
-
- // Figure out the shortest path to rotate directly
- const possibilities = [rTarget - 360, rTarget, rTarget + 360]
- const distances = possibilities.map(a => Math.abs(a - rCurrent))
- const shortest = Math.min(...distances)
- const index = distances.indexOf(shortest)
- target.rotate = possibilities[index]
- }
-
- if (relative) {
- // we have to be careful here not to overwrite the rotation
- // with the rotate method of SVG.Matrix
- if (!isMatrix) {
- target.rotate = transforms.rotate || 0
- }
- if (this._isDeclarative && currentAngle) {
- start.rotate = currentAngle
- }
- }
-
- morpher.from(start)
- morpher.to(target)
-
- let affineParameters = morpher.at(pos)
- currentAngle = affineParameters.rotate
- current = new SVG.Matrix(affineParameters)
-
- this.addTransform(current)
- return morpher.done()
- }
-
- function retarget (newTransforms) {
- // only get a new origin if it changed since the last call
- if (
- (newTransforms.origin || 'center').toString() !==
- (transforms.origin || 'center').toString()
- ) {
- origin = getOrigin(transforms, element)
- }
-
- // overwrite the old transformations with the new ones
- transforms = {...newTransforms, origin}
- }
-
- this.queue(setup, run, retarget)
- this._isDeclarative && this._rememberMorpher('transform', morpher)
- return this
- },
-
- // Animatable x-axis
- x: function (x, relative) {
- return this._queueNumber('x', x)
- },
-
- // Animatable y-axis
- y: function (y) {
- return this._queueNumber('y', y)
- },
-
- dx: function (x) {
- return this._queueNumberDelta('dx', x)
- },
-
- dy: function (y) {
- return this._queueNumberDelta('dy', y)
- },
-
- _queueNumberDelta: function (method, to) {
- to = new SVG.Number(to)
-
- // Try to change the target if we have this method already registerd
- if (this._tryRetargetDelta(method, to)) return this
-
- // Make a morpher and queue the animation
- var morpher = new SVG.Morphable(this._stepper).to(to)
- this.queue(function () {
- var from = this.element()[method]()
- morpher.from(from)
- morpher.to(from + to)
- }, function (pos) {
- this.element()[method](morpher.at(pos))
- return morpher.done()
- })
-
- // Register the morpher so that if it is changed again, we can retarget it
- this._rememberMorpher(method, morpher)
- return this
- },
-
- _queueObject: function (method, to) {
- // Try to change the target if we have this method already registerd
- if (this._tryRetarget(method, to)) return this
-
- // Make a morpher and queue the animation
- var morpher = new SVG.Morphable(this._stepper).to(to)
- this.queue(function () {
- morpher.from(this.element()[method]())
- }, function (pos) {
- this.element()[method](morpher.at(pos))
- return morpher.done()
- })
-
- // Register the morpher so that if it is changed again, we can retarget it
- this._rememberMorpher(method, morpher)
- return this
- },
-
- _queueNumber: function (method, value) {
- return this._queueObject(method, new SVG.Number(value))
- },
-
- // Animatable center x-axis
- cx: function (x) {
- return this._queueNumber('cx', x)
- },
-
- // Animatable center y-axis
- cy: function (y) {
- return this._queueNumber('cy', y)
- },
-
- // Add animatable move
- move: function (x, y) {
- return this.x(x).y(y)
- },
-
- // Add animatable center
- center: function (x, y) {
- return this.cx(x).cy(y)
- },
-
- // Add animatable size
- size: function (width, height) {
- // animate bbox based size for all other elements
- var box
-
- if (!width || !height) {
- box = this._element.bbox()
- }
-
- if (!width) {
- width = box.width / box.height * height
- }
-
- if (!height) {
- height = box.height / box.width * width
- }
-
- return this
- .width(width)
- .height(height)
- },
-
- // Add animatable width
- width: function (width) {
- return this._queueNumber('width', width)
- },
-
- // Add animatable height
- height: function (height) {
- return this._queueNumber('height', height)
- },
-
- // Add animatable plot
- plot: function (a, b, c, d) {
- // Lines can be plotted with 4 arguments
- if (arguments.length === 4) {
- return this.plot([a, b, c, d])
- }
-
- // FIXME: this needs to be rewritten such that the element is only accesed
- // in the init function
- return this._queueObject('plot', new this._element.MorphArray(a))
-
- /*
- var morpher = this._element.morphArray().to(a)
-
- this.queue(function () {
- morpher.from(this._element.array())
- }, function (pos) {
- this._element.plot(morpher.at(pos))
- })
-
- return this
- */
- },
-
- // Add leading method
- leading: function (value) {
- return this._queueNumber('leading', value)
- },
-
- // Add animatable viewbox
- viewbox: function (x, y, width, height) {
- return this._queueObject('viewbox', new SVG.Box(x, y, width, height))
- },
-
- update: function (o) {
- if (typeof o !== 'object') {
- return this.update({
- offset: arguments[0],
- color: arguments[1],
- opacity: arguments[2]
- })
- }
-
- if (o.opacity != null) this.attr('stop-opacity', o.opacity)
- if (o.color != null) this.attr('stop-color', o.color)
- if (o.offset != null) this.attr('offset', o.offset)
-
- return this
- }
-})