- fixed `position` for `transform()` to also allow a position of 0
- fixed `bbox()` of `PathArray` and `PointArray` which returns an instance of `Box` now
- fixed bug in creation of PointArray which had still references to source arrays in it
+ - fixed `PID` controller and makeSetterGetter function
+ - fixed `Queue.push` which didnt let you push queue items
+ - fixed `Timeline.reverse()` which did exactly the opposite of what you would expect when passing `true/false`
+ - fixed cancelAnimationFrame-mock for tests
+ - fixed animate when=after to be really "now" when no runner is on the timeline
### Added
- added second Parameter to `SVG(el, isHTML)` which allows to explicitely create elements in the HTML namespace (#1058)
- added position argument for `toRoot()`
- added attr syntax for `data()` method
- added index and array parameter when passing a function to `List.each()` so that it mostly behaves like map
- - added possibility to pass transform object to `PointArray.transform()` ----- to Point
+ - added possibility to pass a transform object to `PointArray.transform()` similar to Point
+ - added `with-last` as `when` to `animate` and `schedule` to let an animation start with the start of the last one in the timeline
- added lots of tests in es6 format
### Deleted
## Documentation
Check [svgjs.com](https://svgjs.com/docs/3.0/) to learn more.
-[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=pay%40woutfierens.com&lc=US&item_name=SVG.JS¤cy_code=EUR&bn=PP-DonationsBF%3Abtn_donate_74x21.png%3ANonHostedGuest)
+[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=ulima.ums%40googlemail.com&lc=US&item_name=SVG.JS¤cy_code=EUR&bn=PP-DonationsBF%3Abtn_donate_74x21.png%3ANonHostedGuest)
throw new Error('You should pass a function to requestAnimationFrame')
}
- callbacks[index++] = fn
+ const i = index++
+ callbacks[i] = fn
- return index
+ return i
}
/**
import { Animator, Queue } from '../../../src/main.js'
import { getWindow } from '../../../src/utils/window.js'
-describe('Animator.js', function () {
+describe('Animator.js', () => {
- beforeEach(function () {
+ beforeEach(() => {
jasmine.RequestAnimationFrame.install(getWindow())
Animator.timeouts = new Queue()
Animator.frames = new Queue()
+ Animator.immediates = new Queue()
Animator.nextDraw = null
})
- afterEach(function () {
+ afterEach(() => {
jasmine.RequestAnimationFrame.uninstall(getWindow())
})
- describe('timeout()', function () {
- it('calls a function after a specific time', function () {
+ describe('timeout()', () => {
+ it('calls a function after a specific time', () => {
var spy = jasmine.createSpy('tester')
Animator.timeout(spy, 100)
})
})
- describe('cancelTimeout()', function () {
- it('cancels a timeout which was created with timeout()', function () {
+ describe('cancelTimeout()', () => {
+ it('cancels a timeout which was created with timeout()', () => {
var spy = jasmine.createSpy('tester')
var id = Animator.timeout(spy, 100)
Animator.clearTimeout(id)
})
})
- describe('frame()', function () {
- it('calls a function at the next animationFrame', function () {
+ describe('frame()', () => {
+ it('calls a function at the next animationFrame', () => {
var spy = jasmine.createSpy('tester')
Animator.frame(spy)
})
})
+ describe('cancelFrame()', () => {
+ it('cancels a single frame which was created with frame()', () => {
+ var spy = jasmine.createSpy('tester')
+
+ const id = Animator.frame(spy)
+ Animator.cancelFrame(id)
+
+ expect(spy).not.toHaveBeenCalled()
+ jasmine.RequestAnimationFrame.tick()
+ expect(spy).not.toHaveBeenCalled()
+ })
+ })
+
+ describe('immediate()', () => {
+ it('calls a function at the next animationFrame but after all frames are processed', () => {
+ var spy = jasmine.createSpy('tester')
+
+ Animator.immediate(spy)
+
+ expect(spy).not.toHaveBeenCalled()
+ jasmine.RequestAnimationFrame.tick()
+ expect(spy).toHaveBeenCalled()
+ })
+ })
+
+ describe('cancelImmediate()', () => {
+ it('cancels an immediate cakk which was created with immediate()', () => {
+ var spy = jasmine.createSpy('tester')
+
+ const id = Animator.immediate(spy)
+ Animator.cancelImmediate(id)
+
+ expect(spy).not.toHaveBeenCalled()
+ jasmine.RequestAnimationFrame.tick()
+ expect(spy).not.toHaveBeenCalled()
+ })
+ })
})
--- /dev/null
+/* globals describe, expect, it, jasmine */
+
+import { easing, defaults } from '../../../src/main.js'
+import { Stepper, Ease, Controller, Spring, PID } from '../../../src/animation/Controller.js'
+
+const { any, createSpy } = jasmine
+
+describe('Controller.js', () => {
+
+ describe('easing', () => {
+ var easedValues = {
+ '-': 0.5,
+ '<>': 0.5,
+ '>': 0.7071,
+ '<': 0.2929
+ }
+
+ ;[ '-', '<>', '<', '>' ].forEach((el) => {
+ describe(el, () => {
+ it('is 0 at 0', () => {
+ expect(easing[el](0)).toBe(0)
+ })
+ it('is 1 at 1', () => {
+ expect(Math.round(easing[el](1) * 1000) / 1000).toBe(1) // we need to round cause for some reason at some point 1==0.999999999
+ })
+ it('is eased at 0.5', () => {
+ expect(easing[el](0.5)).toBeCloseTo(easedValues[el])
+ })
+ })
+ })
+
+ describe('beziere()', () => {
+ const b1 = easing.bezier(0.25, 0.25, 0.75, 0.75)
+ const b2 = easing.bezier(-0.25, -0.25, 0.75, 0.75)
+ const b3 = easing.bezier(0.5, 0.5, 2, 2)
+ const b4 = easing.bezier(1, 1, 2, 2)
+ const b5 = easing.bezier(-1, -1, -2, -2)
+
+ it('is 0 at 0', () => {
+ expect(b1(0)).toBe(0)
+ })
+
+ it('is 1 at 1', () => {
+ expect(b1(1)).toBe(1)
+ })
+
+ it('is eased at 0.5', () => {
+ expect(b1(0.5)).toBe(0.5)
+ expect(b2(0.5)).toBe(0.3125)
+ expect(b3(0.5)).toBe(1.0625)
+ expect(b4(0.5)).toBe(1.25)
+ expect(b5(0.5)).toBe(-1)
+ })
+
+ it('handles values bigger 1', () => {
+ expect(b1(1.5)).toBe(1.5)
+ expect(b2(1.5)).toBe(1.5)
+ expect(b3(1.5)).toBe(1.5)
+ expect(b4(1.5)).toBe(1)
+ expect(b5(1.5)).toBe(1.5)
+ })
+
+ it('handles values lower 0', () => {
+ expect(b1(-0.5)).toBe(-0.5)
+ expect(b2(-0.5)).toBe(-0.5)
+ expect(b3(-0.5)).toBe(-0.5)
+ expect(b4(-0.5)).toBe(-0.5)
+ expect(b5(-0.5)).toBe(0)
+ })
+ })
+
+ describe('steps()', () => {
+ const s1 = easing.steps(5)
+ const s2 = easing.steps(5, 'start')
+ const s3 = easing.steps(5, 'end')
+ const s4 = easing.steps(5, 'none')
+ const s5 = easing.steps(5, 'both')
+
+ it('is 0 at 0', () => {
+ expect(s1(0)).toBe(0)
+ expect(s1(0, true)).toBe(0)
+ expect(s2(0)).toBe(0.2)
+ expect(s2(0, true)).toBe(0)
+ expect(s3(0)).toBe(0)
+ expect(s3(0, true)).toBe(0)
+ expect(s4(0)).toBe(0)
+ expect(s4(0, true)).toBe(0)
+ expect(s5(0)).toBe(1 / 6)
+ expect(s5(0, true)).toBe(0)
+ })
+
+ it('also works at values < 0', () => {
+ expect(s1(-0.1)).toBe(-0.2)
+ expect(s1(-0.1, true)).toBe(-0.2)
+ expect(s2(-0.1)).toBe(0)
+ expect(s2(-0.1, true)).toBe(0)
+ expect(s3(-0.1)).toBe(-0.2)
+ expect(s3(-0.1, true)).toBe(-0.2)
+ expect(s4(-0.1)).toBe(-0.25)
+ expect(s4(-0.1, true)).toBe(-0.25)
+ expect(s5(-0.1)).toBe(0)
+ expect(s5(-0.1, true)).toBe(0)
+ })
+
+ it('is 1 at 1', () => {
+ expect(s1(1)).toBe(1)
+ expect(s1(1, true)).toBe(0.8)
+ expect(s2(1)).toBe(1)
+ expect(s2(1, true)).toBe(1)
+ expect(s3(1)).toBe(1)
+ expect(s3(1, true)).toBe(0.8)
+ expect(s4(1)).toBe(1)
+ expect(s4(1, true)).toBe(1)
+ expect(s5(1)).toBe(1)
+ expect(s5(1, true)).toBe(5 / 6)
+ })
+
+ it('also works at values > 1', () => {
+ expect(s1(1.1)).toBe(1)
+ expect(s1(1.1, true)).toBe(1)
+ expect(s2(1.1)).toBe(1.2)
+ expect(s2(1.1, true)).toBe(1.2)
+ expect(s3(1.1)).toBe(1)
+ expect(s3(1.1, true)).toBe(1)
+ expect(s4(1.1)).toBe(1.25)
+ expect(s4(1.1, true)).toBe(1.25)
+ expect(s5(1.1)).toBe(1)
+ expect(s5(1.1, true)).toBe(1)
+ })
+
+ it('is eased at 0.1', () => {
+ expect(s1(0.1)).toBe(0)
+ expect(s1(0.1, true)).toBe(0)
+ expect(s2(0.1)).toBe(0.2)
+ expect(s2(0.1, true)).toBe(0)
+ expect(s3(0.1)).toBe(0)
+ expect(s3(0.1, true)).toBe(0)
+ expect(s4(0.1)).toBe(0)
+ expect(s4(0.1, true)).toBe(0)
+ expect(s5(0.1)).toBe(1 / 6)
+ expect(s5(0.1, true)).toBe(0)
+ })
+
+ it('is eased at 0.15', () => {
+ expect(s1(0.15)).toBe(0)
+ expect(s1(0.15, true)).toBe(0)
+ expect(s2(0.15)).toBe(0.2)
+ expect(s2(0.15, true)).toBe(0)
+ expect(s3(0.15)).toBe(0)
+ expect(s3(0.15, true)).toBe(0)
+ expect(s4(0.15)).toBe(0)
+ expect(s4(0.15, true)).toBe(0)
+ expect(s5(0.15)).toBe(1 / 6)
+ expect(s5(0.15, true)).toBe(0)
+ })
+
+ it('is eased at 0.2', () => {
+ expect(s1(0.2)).toBe(0.2)
+ expect(s1(0.2, true)).toBe(0.2)
+ expect(s2(0.2)).toBe(0.4)
+ expect(s2(0.2, true)).toBe(0.4)
+ expect(s3(0.2)).toBe(0.2)
+ expect(s3(0.2, true)).toBe(0.2)
+ expect(s4(0.2)).toBe(0.25)
+ expect(s4(0.2, true)).toBe(0.25)
+ expect(s5(0.2)).toBe(1 / 3)
+ expect(s5(0.2, true)).toBe(1 / 3)
+ })
+
+ it('is eased at 0.25', () => {
+ expect(s1(0.25)).toBe(0.2)
+ expect(s1(0.25, true)).toBe(0.2)
+ expect(s2(0.25)).toBe(0.4)
+ expect(s2(0.25, true)).toBe(0.4)
+ expect(s3(0.25)).toBe(0.2)
+ expect(s3(0.25, true)).toBe(0.2)
+ expect(s4(0.25)).toBe(0.25)
+ expect(s4(0.25, true)).toBe(0.25)
+ expect(s5(0.25)).toBe(1 / 3)
+ expect(s5(0.25, true)).toBe(1 / 3)
+ })
+ })
+ })
+
+ describe('Stepper', () => {
+ it('has a done() method', () => {
+ const stepper = new Stepper()
+ expect(stepper).toEqual(any(Stepper))
+ expect(stepper.done()).toBe(false)
+ })
+ })
+
+ describe('Ease', () => {
+ describe('()', () => {
+ it('wraps the default easing function by default', () => {
+ const ease = new Ease()
+ expect(ease.ease).toBe(easing[defaults.timeline.ease])
+ })
+
+ it('wraps an easing function found in "easing"', () => {
+ const ease = new Ease('-')
+ expect(ease.ease).toBe(easing['-'])
+ })
+
+ it('wraps a a custom easing function', () => {
+ const ease = new Ease(easing['-'])
+ expect(ease.ease).toBe(easing['-'])
+ })
+ })
+
+ describe('step()', () => {
+ it('provides an eased value to a position', () => {
+ let ease = new Ease(easing['-'])
+ expect(ease.step(2, 4, 0.5)).toBe(3)
+
+ ease = new Ease(() => 3)
+ expect(ease.step(2, 4, 0.5)).toBe(8)
+
+ ease = new Ease()
+ expect(ease.step(2, 4, 0.5)).toBeCloseTo(3.414, 3)
+ })
+
+ it('jumps to "to" value at pos 1 if from is not a number', () => {
+ const ease = new Ease('-')
+ expect(ease.step('Hallo', 'Welt', 0.999)).toBe('Hallo')
+ expect(ease.step('Hallo', 'Welt', 1)).toBe('Welt')
+ })
+ })
+ })
+
+ describe('Controller', () => {
+ describe('()', () => {
+ it('constructs a controller with the given stepper function set', () => {
+ const spy = createSpy()
+ const controller = new Controller(spy)
+ expect(controller).toEqual(any(Controller))
+ expect(controller.stepper).toBe(spy)
+ })
+ })
+
+ describe('step()', () => {
+ it('runs the stepper with current value, target value, dt and context', () => {
+ const spy = createSpy().and.returnValue('foo')
+ const controller = new Controller(spy)
+ expect(controller.step(10, 20, 30, 'bar')).toBe('foo')
+ expect(spy).toHaveBeenCalledWith(10, 20, 30, 'bar')
+ })
+ })
+
+ describe('done()', () => {
+ it('returns given values "done" property', () => {
+ const spy = createSpy()
+ const controller = new Controller(spy)
+ expect(controller.done({ done: 'yes' })).toBe('yes')
+ })
+ })
+ })
+
+ describe('Spring', () => {
+ describe('()', () => {
+ it('creates a spring with default duration and overshoot', () => {
+ const spring = new Spring()
+ expect(spring).toEqual(any(Spring))
+ expect(spring.duration()).toBe(500)
+ expect(spring.overshoot()).toBe(0)
+ })
+
+ it('creates a spring with given duration and overshoot', () => {
+ const spring = new Spring(100, 10)
+ expect(spring).toEqual(any(Spring))
+ expect(spring.duration()).toBe(100)
+ expect(spring.overshoot()).toBe(10)
+ })
+ })
+
+ describe('duration()', () => {
+ it('gets and sets a new duration for the spring controller', () => {
+ const spring = new Spring().duration(100)
+ expect(spring.duration()).toBe(100)
+ })
+ })
+
+ describe('overshoot()', () => {
+ it('gets and sets a new overshoot for the spring controller', () => {
+ const spring = new Spring().overshoot(10)
+ expect(spring.overshoot()).toBe(10)
+ })
+ })
+
+ describe('step()', () => {
+ it('calculates the new spring position', () => {
+ const spring = new Spring()
+ expect(spring.step(0, 100, 16, {})).toBeCloseTo(0.793, 3)
+ })
+
+ it('returns current if current is a string', () => {
+ const spring = new Spring()
+ expect(spring.step('Hallo', 'Welt', 16, {})).toBe('Hallo')
+ })
+
+ it('returns current if dt is 0', () => {
+ const spring = new Spring()
+ expect(spring.step(0, 100, 0, {})).toBe(0)
+ })
+
+ it('is done if dt is infinity and returns target', () => {
+ const spring = new Spring()
+ const context = {}
+ expect(spring.step(0, 100, Infinity, context)).toBe(100)
+ expect(spring.done(context)).toBe(true)
+ })
+
+ it('uses dt of 16 if it is over 100', () => {
+ const spring = new Spring()
+ expect(spring.step(0, 100, 101, {})).toBe(spring.step(0, 100, 16, {}))
+ })
+ })
+ })
+
+ describe('PID', () => {
+ describe('()', () => {
+ it('creates a PID controller with default values', () => {
+ const pid = new PID()
+ expect(pid).toEqual(any(PID))
+ expect(pid.p()).toBe(0.1)
+ expect(pid.i()).toBe(0.01)
+ expect(pid.d()).toBe(0)
+ expect(pid.windup()).toBe(1000)
+ })
+
+ it('creates a PID controller with given values', () => {
+ const pid = new PID(1, 2, 3, 4)
+ expect(pid).toEqual(any(PID))
+ expect(pid.p()).toBe(1)
+ expect(pid.i()).toBe(2)
+ expect(pid.d()).toBe(3)
+ expect(pid.windup()).toBe(4)
+ })
+ })
+
+ describe('p()', () => {
+ it('gets and sets the p parameter of the controller', () => {
+ const pid = new PID().p(100)
+ expect(pid.p()).toBe(100)
+ })
+ })
+
+ describe('i()', () => {
+ it('gets and sets the i parameter of the controller', () => {
+ const pid = new PID().i(100)
+ expect(pid.i()).toBe(100)
+ })
+ })
+
+ describe('d()', () => {
+ it('gets and sets the d parameter of the controller', () => {
+ const pid = new PID().d(100)
+ expect(pid.d()).toBe(100)
+ })
+ })
+
+ describe('windup()', () => {
+ it('gets and sets the windup parameter of the controller', () => {
+ const pid = new PID().windup(100)
+ expect(pid.windup()).toBe(100)
+ })
+ })
+
+ describe('step()', () => {
+ it('returns current if current is a string', () => {
+ const pid = new PID()
+ expect(pid.step('Hallo', 'Welt', 16, {})).toBe('Hallo')
+ })
+
+ it('returns current if dt is 0', () => {
+ const pid = new PID()
+ expect(pid.step(0, 100, 0, {})).toBe(0)
+ })
+
+ it('is done if dt is infinity and returns target', () => {
+ const pid = new PID()
+ const context = {}
+ expect(pid.step(0, 100, Infinity, context)).toBe(100)
+ expect(pid.done(context)).toBe(true)
+ })
+
+ it('caculates a new value', () => {
+ const pid = new PID()
+ expect(pid.step(0, 100, 16, {})).toBe(20)
+ })
+
+ it('uses antiwindup to restrict i power', () => {
+ const pid = new PID(0, 5, 0, 100)
+ expect(pid.step(0, 100, 1000, {})).toBe(500)
+ })
+
+ it('doesnt uses antiwindup if disabled', () => {
+ const pid = new PID(0, 5, 0, false)
+ expect(pid.step(0, 100, 1000, {})).toBe(500000)
+ })
+ })
+ })
+})
/* globals describe, expect, it, jasmine */
import { Morphable, NonMorphable, ObjectBag, Color, Box, Matrix, PointArray, PathArray, TransformBag, Number as SVGNumber, Array as SVGArray } from '../../../src/main.js'
+import { Stepper, easing, Ease } from '../../../src/animation/Controller.js'
const { objectContaining, arrayContaining, any } = jasmine
-describe('Morphable.js', function () {
- describe('constructors', function () {
+describe('Morphable.js', () => {
+ describe('()', () => {
+ it('sets a default stepper', () => {
+ const morpher = new Morphable()
+ expect(morpher.stepper().ease).toBe(easing['-'])
+ })
+
+ it('sets the passed stepper', () => {
+ const ease = new Ease()
+ const morpher = new Morphable(ease)
+ expect(morpher.stepper()).toBe(ease)
+ })
+ })
+
+ describe('constructors', () => {
- it('Morphable with SVGNumber', function () {
+ it('Morphable with SVGNumber', () => {
var morpher = new Morphable().from(10).to(5)
expect(morpher).toEqual(any(Morphable))
expect(morpher.at(0.5).valueOf()).toBe(7.5)
})
- it('Morphable with String', function () {
+ it('Morphable with String', () => {
var morpher = new Morphable().from('foo').to('bar')
expect(morpher).toEqual(any(Morphable))
expect(morpher.at(1).valueOf()).toBe('bar')
})
- it('Morphable with Object', function () {
+ it('Morphable with Object', () => {
var morpher = new Morphable().from({ a: 5, b: 10 }).to({ a: 10, b: 20 })
expect(morpher).toEqual(any(Morphable))
expect(morpher.at(0.5).valueOf()).toEqual(objectContaining({ a: 7.5, b: 15 }))
})
- it('Creates a morphable out of an SVGNumber', function () {
+ it('Creates a morphable out of an SVGNumber', () => {
var morpher = new SVGNumber(5).to(10)
expect(morpher).toEqual(any(Morphable))
expect(morpher.at(0.5).valueOf()).toBe(7.5)
})
- it('Creates a morphable out of an Color', function () {
+ it('Creates a morphable out of an Color', () => {
var morpher = new Color('#fff').to('#000')
expect(morpher).toEqual(any(Morphable))
expect(morpher.at(0.5).toHex()).toBe('#808080')
})
- it('Creates a morphable out of an Box', function () {
+ it('Creates a morphable out of an Box', () => {
var morpher = new Box(1, 2, 3, 4).to([ 5, 6, 7, 8 ])
expect(morpher).toEqual(any(Morphable))
expect(morpher.at(0.5)).toEqual(objectContaining({ x: 3, y: 4, width: 5, height: 6 }))
})
- it('Creates a morphable out of an Matrix', function () {
+ it('Creates a morphable out of an Matrix', () => {
var morpher = new Matrix(1, 2, 3, 4, 5, 6).to([ 3, 4, 5, 6, 7, 8 ])
expect(morpher).toEqual(any(Morphable))
expect(morpher.at(0.5)).toEqual(objectContaining(new Matrix(2, 3, 4, 5, 6, 7)))
})
- it('Creates a morphable out of an Array', function () {
+ it('Creates a morphable out of an SVGArray', () => {
var morpher = new SVGArray([ 1, 2, 3, 4, 5, 6 ]).to([ 3, 4, 5, 6, 7, 8 ])
expect(morpher).toEqual(any(Morphable))
expect(morpher.at(0.5).toArray()).toEqual(arrayContaining([ 2, 3, 4, 5, 6, 7 ]))
})
- it('Creates a morphable out of an PointArray', function () {
+ it('Creates a morphable out of an PointArray', () => {
var morpher = new PointArray([ 1, 2, 3, 4, 5, 6 ]).to([ 3, 4, 5, 6, 7, 8 ])
expect(morpher).toEqual(any(Morphable))
expect(morpher.at(0.5).toArray()).toEqual(arrayContaining([ 2, 3, 4, 5, 6, 7 ]))
})
- it('Creates a morphable out of an PathArray', function () {
+ it('Creates a morphable out of an PathArray', () => {
var morpher = new PathArray([ 'M', 1, 2, 'L', 3, 4, 'L', 5, 6 ]).to([ 'M', 3, 4, 'L', 5, 6, 'L', 7, 8 ])
expect(morpher).toEqual(any(Morphable))
expect(morpher.at(0.5).toArray()).toEqual(arrayContaining([ 'M', 2, 3, 'L', 4, 5, 'L', 6, 7 ]))
})
- it('Creates a morphable out of an NonMorphable', function () {
- var morpher = new NonMorphable('foo').to('bar')
+ it('creates a morphable from unmorphable types', () => {
+ var morpher = new Morphable().from('Hallo').to('Welt')
expect(morpher).toEqual(any(Morphable))
expect(morpher.type()).toBe(NonMorphable)
expect(morpher.at(0.5)).toEqual(any(NonMorphable))
- expect(morpher.at(0.5).valueOf()).toBe('foo')
- expect(morpher.at(1).valueOf()).toBe('bar')
+ expect(morpher.at(0.5).valueOf()).toBe('Hallo')
+ expect(morpher.at(1).valueOf()).toBe('Welt')
})
- it('Creates a morphable out of an TransformBag', function () {
+ it('Creates a morphable out of an TransformBag', () => {
var morpher = new TransformBag({ rotate: 0, translateX: 0 })
.to({ rotate: 50, translateX: 20 })
expect(morpher.at(0.5)).toEqual(objectContaining({ rotate: 25, translateX: 10 }))
})
- it('Creates a morphable out of an ObjectBag', function () {
+ it('Creates a morphable out of an ObjectBag', () => {
var morpher = new ObjectBag({ a: 5, b: 10 }).to({ a: 10, b: 20 })
expect(morpher).toEqual(any(Morphable))
expect(morpher.at(0.5)).toEqual(any(Object))
expect(morpher.at(0.5).valueOf()).toEqual(objectContaining({ a: 7.5, b: 15 }))
})
+
+ it('creates a morphable from a color string', () => {
+ var morpher = new Morphable().from('#fff').to('#000')
+
+ expect(morpher).toEqual(any(Morphable))
+ expect(morpher.type()).toBe(Color)
+ expect(morpher.at(0.5)).toEqual(any(Color))
+ expect(morpher.at(0.5).toHex()).toBe('#808080')
+
+ morpher = new Morphable().from('rgb(255,255,255)').to('rgb(0,0,0)')
+
+ expect(morpher).toEqual(any(Morphable))
+ expect(morpher.type()).toBe(Color)
+ expect(morpher.at(0.5)).toEqual(any(Color))
+ expect(morpher.at(0.5).toHex()).toBe('#808080')
+ })
+
+ it('creates a morphable from path string', () => {
+ var morpher = new Morphable().from('M 0 0 L 10 10').to('M 0 0 L 20 20')
+
+ expect(morpher).toEqual(any(Morphable))
+ expect(morpher.type()).toBe(PathArray)
+ expect(morpher.at(0.5)).toEqual(any(PathArray))
+ expect(morpher.at(0.5).toString()).toBe('M0 0L15 15 ')
+ })
+
+ it('creates a morphable from number string', () => {
+ var morpher = new Morphable().from('10').to('20')
+
+ expect(morpher).toEqual(any(Morphable))
+ expect(morpher.type()).toBe(SVGNumber)
+ expect(morpher.at(0.5)).toEqual(any(SVGNumber))
+ expect(morpher.at(0.5).toString()).toBe('15')
+
+ morpher = new Morphable().from('10px').to('20px')
+
+ expect(morpher).toEqual(any(Morphable))
+ expect(morpher.type()).toBe(SVGNumber)
+ expect(morpher.at(0.5)).toEqual(any(SVGNumber))
+ expect(morpher.at(0.5).toString()).toBe('15px')
+ })
+
+ it('creates a morphable from delimited string', () => {
+ var morpher = new Morphable().from(' 0 1, 2 , 3 ').to('4,5,6,7')
+
+ expect(morpher).toEqual(any(Morphable))
+ expect(morpher.type()).toBe(SVGArray)
+ expect(morpher.at(0.5)).toEqual(any(SVGArray))
+ expect(morpher.at(0.5)).toEqual([ 2, 3, 4, 5 ])
+ })
+
+ it('creates a morphable from an array', () => {
+ var morpher = new Morphable().from([ 0, 1, 2, 3 ]).to([ 4, 5, 6, 7 ])
+
+ expect(morpher).toEqual(any(Morphable))
+ expect(morpher.type()).toBe(SVGArray)
+ expect(morpher.at(0.5)).toEqual(any(SVGArray))
+ expect(morpher.at(0.5)).toEqual([ 2, 3, 4, 5 ])
+ })
+
+ it('converts the to-color to the from-type', () => {
+ var morpher = new Color('#fff').to(new Color(1, 2, 3, 'hsl'))
+ expect(new Color(morpher.from()).space).toBe('rgb')
+ expect(morpher.at(0.5).space).toBe('rgb')
+ })
+
+ it('converts the from-color to the to-type', () => {
+ const morpher = new Morphable().to(new Color(1, 2, 3, 'hsl')).from('#fff')
+ expect(new Color(morpher.from()).space).toBe('hsl')
+ expect(morpher.at(0.5).space).toBe('hsl')
+ })
})
- describe('from()', function () {
- it('sets the type of the runner', function () {
+ describe('from()', () => {
+ it('sets the type of the runner', () => {
var morpher = new Morphable().from(5)
expect(morpher.type()).toBe(SVGNumber)
})
- it('sets the from attribute to an array representation of the morphable type', function () {
+ it('sets the from attribute to an array representation of the morphable type', () => {
var morpher = new Morphable().from(5)
expect(morpher.from()).toEqual(arrayContaining([ 5 ]))
})
})
- describe('type()', function () {
- it('sets the type of the runner', function () {
+ describe('type()', () => {
+ it('sets the type of the runner', () => {
var morpher = new Morphable().type(SVGNumber)
expect(morpher._type).toBe(SVGNumber)
})
- it('gets the type of the runner', function () {
+ it('gets the type of the runner', () => {
var morpher = new Morphable().type(SVGNumber)
expect(morpher.type()).toBe(SVGNumber)
})
})
- describe('to()', function () {
- it('sets the type of the runner', function () {
+ describe('to()', () => {
+ it('sets the type of the runner', () => {
var morpher = new Morphable().to(5)
expect(morpher.type()).toBe(SVGNumber)
})
- it('sets the from attribute to an array representation of the morphable type', function () {
+ it('sets the from attribute to an array representation of the morphable type', () => {
var morpher = new Morphable().to(5)
expect(morpher.to()).toEqual(arrayContaining([ 5 ]))
})
})
+
+ describe('stepper()', () => {
+ it('sets and gets the stepper of the Morphable', () => {
+ const stepper = new Stepper()
+ const morpher = new Morphable().stepper(stepper)
+ expect(morpher.stepper()).toBe(stepper)
+ })
+ })
+
+ describe('NonMorphable', () => {
+ describe('()', () => {
+ it('wraps any type into a NonMorphable from an array', () => {
+ const non = new NonMorphable([ 5 ])
+ expect(non.valueOf()).toBe(5)
+ })
+
+ it('wraps any type into a NonMorphable from any type', () => {
+ expect(new NonMorphable(5).valueOf()).toBe(5)
+ expect(new NonMorphable('Hello').valueOf()).toBe('Hello')
+ })
+ })
+
+ describe('toArray()', () => {
+ it('returns array representation of NonMorphable', () => {
+ expect(new NonMorphable(5).toArray()).toEqual([ 5 ])
+ expect(new NonMorphable('Hello').toArray()).toEqual([ 'Hello' ])
+ })
+ })
+ })
+
+ describe('TransformBag', () => {
+ describe('()', () => {
+ it('creates an object which holds transformations for morphing by passing array', () => {
+ const bag = new TransformBag([ 0, 1, 2, 3, 4, 5, 6, 7 ])
+ expect(bag.toArray()).toEqual([ 0, 1, 2, 3, 4, 5, 6, 7 ])
+ })
+
+ it('creates an object which holds transformations for morphing by passing object', () => {
+ const bag = new TransformBag({
+ scaleX: 0,
+ scaleY: 1,
+ shear: 2,
+ rotate: 3,
+ translateX: 4,
+ translateY: 5,
+ originX: 6,
+ originY: 7
+ })
+
+ expect(bag.toArray()).toEqual([ 0, 1, 2, 3, 4, 5, 6, 7 ])
+ })
+ })
+
+ describe('toArray()', () => {
+ it('creates an array out of the transform values', () => {
+ const bag = new TransformBag([ 0, 1, 2, 3, 4, 5, 6, 7 ])
+ expect(bag.toArray()).toEqual([ 0, 1, 2, 3, 4, 5, 6, 7 ])
+ })
+ })
+ })
+
+ describe('ObjectBag', () => {
+ describe('()', () => {
+ it('wraps an object into a morphable object by passing an array', () => {
+ const bag = new ObjectBag([ 'foo', 1, 'bar', 2, 'baz', 3 ])
+ expect(bag.values).toEqual([ 'foo', 1, 'bar', 2, 'baz', 3 ])
+ })
+
+ it('wraps an object into a morphable object by passing an object', () => {
+ const bag = new ObjectBag({ foo: 1, bar: 2, baz: 3 })
+ expect(bag.values).toEqual([ 'bar', 2, 'baz', 3, 'foo', 1 ])
+ })
+ })
+
+ describe('toArray()', () => {
+ it('creates an array out of the object', () => {
+ const bag = new ObjectBag({ foo: 1, bar: 2, baz: 3 })
+ expect(bag.toArray()).toEqual([ 'bar', 2, 'baz', 3, 'foo', 1 ])
+ })
+ })
+
+ describe('valueOf()', () => {
+ it('create an object from the stored values', () => {
+ const bag = new ObjectBag({ foo: 1, bar: 2, baz: 3 })
+ expect(bag.valueOf()).toEqual({ foo: 1, bar: 2, baz: 3 })
+ })
+ })
+ })
})
expect(queue.first()).toBe(1)
expect(queue.last()).toBe(3)
})
+
+ it('adds an item to the end of the queue', function () {
+ var queue = new Queue()
+ queue.push(1)
+ const item = queue.push(2)
+ queue.push(3)
+ queue.remove(item)
+ queue.push(item)
+
+ expect(queue.first()).toBe(1)
+ expect(queue.last()).toBe(2)
+ })
})
describe('remove ()', function () {
expect(queue.last()).toBe(2)
expect(queue.first()).toBe(1)
})
+
+ it('removes the given item from the queue', function () {
+ var queue = new Queue()
+ var item = queue.push(1)
+ queue.push(2)
+ queue.push(3)
+
+ queue.remove(item)
+
+ expect(queue.last()).toBe(3)
+ expect(queue.first()).toBe(2)
+ })
})
describe('shift ()', function () {
-/* globals describe, expect, it, beforeEach, container */
+/* globals describe, expect, it, beforeEach, afterEach, spyOn, container, jasmine */
-import { Timeline, SVG } from '../../../src/main.js'
+import { Timeline, SVG, Runner, Animator, Queue, Rect } from '../../../src/main.js'
+import { getWindow } from '../../../src/utils/window.js'
+
+const { createSpy, any } = jasmine
describe('Timeline.js', () => {
+ beforeEach(() => {
+ jasmine.RequestAnimationFrame.install(getWindow())
+ Animator.timeouts = new Queue()
+ Animator.frames = new Queue()
+ Animator.immediates = new Queue()
+ Animator.nextDraw = null
+ })
+
+ afterEach(() => {
+ getWindow().cancelAnimationFrame(Animator.nextDraw)
+ jasmine.RequestAnimationFrame.uninstall(getWindow())
+ })
+
+ describe('()', () => {
+ it('creates a new Timeline with a default timesource', () => {
+ const timeline = new Timeline()
+ expect(timeline.source()).toEqual(any(Function))
+ })
+
+ it('creates a new Timeline with the passed timesource', () => {
+ const source = createSpy()
+ const timeline = new Timeline(source)
+ expect(timeline.source()).toBe(source)
+ })
+ })
+
+ describe('schedule()', () => {
+ it('schedules a runner at the start of the queue with a default delay of 0', () => {
+ const timeline = new Timeline()
+ const runner = new Runner(1000)
+ timeline.schedule(runner)
+ expect(timeline._runners[0].start).toEqual(0)
+ })
+
+ it('sets a reference of the timeline to the runner', () => {
+ const timeline = new Timeline()
+ const runner = new Runner(1000)
+ timeline.schedule(runner)
+ expect(runner.timeline()).toBe(timeline)
+ })
+
+ it('schedules after when no when is past', () => {
+ const timeline = new Timeline().schedule(new Runner(1000))
+ const runner = new Runner(1000)
+ timeline.schedule(runner)
+ expect(timeline._runners[1].start).toBe(1000)
+ })
+
+ it('schedules after when when is last', () => {
+ const timeline = new Timeline().schedule(new Runner(1000))
+ const runner = new Runner(1000)
+ timeline.schedule(runner, 0, 'last')
+ expect(timeline._runners[1].start).toBe(1000)
+ })
+
+ it('schedules after when when is after', () => {
+ const timeline = new Timeline().schedule(new Runner(1000))
+ const runner = new Runner(1000)
+ timeline.schedule(runner, 0, 'after')
+ expect(timeline._runners[1].start).toBe(1000)
+ })
+
+ it('starts the animation right away when there is no runner to schedule after and when is after', () => {
+ const timeline = new Timeline().time(100)
+ const runner = new Runner(1000)
+ timeline.schedule(runner, 0, 'after')
+ expect(timeline._runners[0].start).toBe(100)
+ })
+
+ it('schedules with start of the last runner when when is with-last', () => {
+ const timeline = new Timeline().schedule(new Runner(1000), 200)
+ const runner = new Runner(1000)
+ timeline.schedule(runner, 0, 'with-last')
+ expect(timeline._runners[1].start).toBe(200)
+ })
+
+ it('starts the animation right away when there is no runner to schedule after and when is after', () => {
+ const timeline = new Timeline().time(100)
+ const runner = new Runner(1000)
+ timeline.schedule(runner, 0, 'with-last')
+ expect(timeline._runners[0].start).toBe(100)
+ })
+
+ it('respects passed delay', () => {
+ const timeline = new Timeline().schedule(new Runner(1000), 1000)
+ const runner = new Runner(1000)
+ timeline.schedule(runner, 0, 'after')
+ expect(timeline._runners[1].start).toBe(2000)
+ })
+
+ it('schedules the runner absolutely with absolute', () => {
+ const timeline = new Timeline().schedule(new Runner(1000))
+ const runner = new Runner(1000)
+ timeline.schedule(runner, 0, 'absolute')
+ expect(timeline._runners[1].start).toBe(0)
+ })
+
+ it('schedules the runner absolutely with start', () => {
+ const timeline = new Timeline().schedule(new Runner(1000))
+ const runner = new Runner(1000)
+ timeline.schedule(runner, 0, 'start')
+ expect(timeline._runners[1].start).toBe(0)
+ })
+
+ it('schedules the runner relatively to old start with relative', () => {
+ const timeline = new Timeline()
+ const runner = new Runner(1000)
+ timeline.schedule(runner, 100).schedule(runner, 100, 'relative')
+ expect(timeline._runners[0].start).toBe(200)
+ })
+
+ it('schedules the runner as absolute if this runner wasnt on the timeline', () => {
+ const timeline = new Timeline()
+ const runner = new Runner(1000)
+ timeline.schedule(runner, 100, 'relative')
+ expect(timeline._runners[0].start).toBe(100)
+ })
+
+ it('throws if when is not supported', () => {
+ const timeline = new Timeline().schedule(new Runner(1000), 1000)
+ const runner = new Runner(1000)
+ expect(() => timeline.schedule(runner, 0, 'not supported')).toThrowError('Invalid value for the "when" parameter')
+ })
+
+ it('uses persist value of the runner of present', () => {
+ const timeline = new Timeline()
+ const runner = new Runner(1000).persist(100)
+ timeline.schedule(runner)
+ expect(timeline._runners[0].persist).toBe(100)
+ })
+ })
+
+ describe('unschedule()', () => {
+ it('removes a runner from the timeline', () => {
+ const timeline = new Timeline()
+ const runner = new Runner(1000)
+ timeline.schedule(runner).unschedule(runner)
+ expect(runner.timeline()).toBe(null)
+ expect(timeline._runners).toEqual([])
+ })
+ })
+
+ describe('getRunnerInfoById()', () => {
+ it('gets a runner by its id from the timeline', () => {
+ const timeline = new Timeline()
+ const runner = new Runner(1000)
+ expect(timeline.schedule(runner).getRunnerInfoById(runner.id).runner).toBe(runner)
+ })
+
+ it('returns null of runner not found', () => {
+ const timeline = new Timeline()
+ const runner = new Runner(1000)
+ expect(timeline.getRunnerInfoById(runner.id)).toBe(null)
+ })
+ })
+
+ describe('getLastRunnerInfo()', () => {
+ it('gets a runner by its id from the timeline', () => {
+ const timeline = new Timeline().schedule(new Runner(1000))
+ const runner = new Runner(1000)
+ expect(timeline.schedule(runner).getLastRunnerInfo().runner).toBe(runner)
+ })
+ })
+
+ describe('getEndTime()', () => {
+ it('returns the end time of the runner which started last', () => {
+ const timeline = new Timeline()
+ const runner = new Runner(1000)
+ const runner2 = new Runner(100)
+ timeline.schedule(runner).schedule(runner2, 500, 'start')
+ expect(timeline.getEndTime()).toBe(600)
+ })
+
+ it('returns the timeline time if no runner is scheduled', () => {
+ const timeline = new Timeline().time(100)
+ expect(timeline.getEndTime()).toBe(100)
+ })
+ })
+
describe('getEndTimeOfTimeline', () => {
it('returns 0 if no runners are scheduled', () => {
const timeline = new Timeline()
const endTime = timeline.getEndTimeOfTimeline()
expect(endTime).toEqual(0)
})
+
+ it('returns the time all runners are finished', () => {
+ const timeline = new Timeline()
+ const runner = new Runner(1000)
+ const runner2 = new Runner(100)
+ timeline.schedule(runner).schedule(runner2, 500, 'start')
+ expect(timeline.getEndTimeOfTimeline()).toBe(1000)
+ })
})
describe('finish - issue #964', () => {
expect(rect3.y()).toEqual(200)
})
})
+
+ describe('updateTime()', () => {
+ it('sets the time to the current time', () => {
+ const timeline = new Timeline(() => 200).play()
+ expect(timeline._lastSourceTime).toBe(200)
+ })
+ })
+
+ describe('stop()', () => {
+ it('sets the time to 0 and pauses the timeline', () => {
+ const timeline = new Timeline().time(100)
+ expect(timeline.stop().time()).toBe(0)
+ expect(timeline._paused).toBe(true)
+ })
+ })
+
+ describe('speed()', () => {
+ it('gets or sets the speed of the timeline', () => {
+ const timeline = new Timeline().speed(2)
+ expect(timeline.speed()).toBe(2)
+ })
+ })
+
+ describe('reverse()', () => {
+ it('reverses the timeline with no parameter given', () => {
+ const timeline = new Timeline().speed(2)
+ const spy = spyOn(timeline, 'speed').and.callThrough()
+ timeline.reverse()
+ expect(spy).toHaveBeenCalledWith(-2)
+ timeline.reverse()
+ expect(spy).toHaveBeenCalledWith(2)
+ })
+
+ it('reverses the timeline when true was passed', () => {
+ const timeline = new Timeline().speed(2)
+ const spy = spyOn(timeline, 'speed').and.callThrough()
+ timeline.reverse(true)
+ expect(spy).toHaveBeenCalledWith(-2)
+ })
+
+ it('plays normal direction when false was passed', () => {
+ const timeline = new Timeline().speed(-2)
+ const spy = spyOn(timeline, 'speed').and.callThrough()
+ timeline.reverse(false)
+ expect(spy).toHaveBeenCalledWith(2)
+ })
+ })
+
+ describe('seek()', () => {
+ it('seeks the time by a given delta', () => {
+ const timeline = new Timeline().time(100).seek(200)
+ expect(timeline.time()).toBe(300)
+ })
+ })
+
+ describe('time()', () => {
+ it('gets and sets the current time of the timeline', () => {
+ const timeline = new Timeline().time(100)
+ expect(timeline.time()).toBe(100)
+ })
+ })
+
+ describe('persist()', () => {
+ it('gets and sets the persist property of the timeline', () => {
+ const timeline = new Timeline().persist(true)
+ expect(timeline.persist()).toBe(true)
+ })
+ })
+
+ describe('source()', () => {
+ it('gets or sets the time source of the timeline', () => {
+ const source = () => 200
+ const timeline = new Timeline().source(source)
+ expect(timeline.source()).toBe(source)
+ })
+ })
+
+ describe('_stepFn', () => {
+ it('does a step in the timeline and runs all runners', () => {
+ const timeline = new Timeline()
+ const runner = new Runner(1000)
+ timeline.schedule(runner).play() // we have to play because its syncronous here
+ jasmine.RequestAnimationFrame.tick(16)
+ expect(runner.time()).toBe(16)
+ })
+
+ it('doenst run runners which start later', () => {
+ const timeline = new Timeline()
+ const runner = new Runner(1000)
+ timeline.schedule(runner, 100).play() // we have to play because its syncronous here
+ jasmine.RequestAnimationFrame.tick(16)
+ expect(runner.time()).toBe(0)
+ })
+
+ it('reset runner if timeline was seeked backwards', () => {
+ const timeline = new Timeline()
+ const runner = new Runner(1000)
+ timeline.schedule(runner)
+ const spy = spyOn(runner, 'reset').and.callThrough()
+ jasmine.RequestAnimationFrame.tick(1000)
+ timeline.seek(-1000)
+ expect(runner.time()).toBe(0)
+ expect(spy).toHaveBeenCalled()
+ })
+
+ it('doesnt run runners if they are not active', () => {
+ const timeline = new Timeline()
+ const runner = new Runner(1000).active(false)
+ timeline.schedule(runner).play() // we have to play because its syncronous here
+ jasmine.RequestAnimationFrame.tick(16)
+ expect(runner.time()).toBe(0)
+ })
+
+ it('unschedules runner if its finished', () => {
+ const timeline = new Timeline()
+ const runner = new Runner(1000)
+ timeline.schedule(runner).play() // we have to play because its syncronous here
+ jasmine.RequestAnimationFrame.tick(1000)
+ jasmine.RequestAnimationFrame.tick(1)
+ expect(runner.time()).toBe(1001)
+ expect(timeline.getRunnerInfoById(runner.id)).toBe(null)
+ })
+
+ it('does not unschedule if runner is persistent forever', () => {
+ const timeline = new Timeline()
+ const runner = new Runner(1000).persist(true)
+ timeline.schedule(runner).play() // we have to play because its syncronous here
+ jasmine.RequestAnimationFrame.tick(1000)
+ jasmine.RequestAnimationFrame.tick(1)
+ expect(runner.time()).toBe(1001)
+ expect(timeline.getRunnerInfoById(runner.id)).not.toBe(null)
+ })
+
+ it('does not unschedule if runner is persistent for a certain time', () => {
+ const timeline = new Timeline()
+ const runner = new Runner(1000).persist(100)
+ timeline.schedule(runner).play() // we have to play because its syncronous here
+ jasmine.RequestAnimationFrame.tick(1000)
+ jasmine.RequestAnimationFrame.tick(1)
+ expect(runner.time()).toBe(1001)
+ expect(timeline.getRunnerInfoById(runner.id)).not.toBe(null)
+ })
+
+ it('fires finish if no runners left', () => {
+ const spy = createSpy()
+ const timeline = new Timeline().on('finished', spy)
+ const runner = new Runner(1000)
+ spy.calls.reset()
+ timeline.schedule(runner).play() // we have to play because its syncronous here
+ jasmine.RequestAnimationFrame.tick(1000)
+ jasmine.RequestAnimationFrame.tick(1)
+ expect(spy).toHaveBeenCalled()
+ })
+
+ it('continues if there are still runners left from us when going back in time', () => {
+ const spy = createSpy()
+ const timeline = new Timeline().on('finished', spy).time(1200).reverse(true)
+ const runner = new Runner(1000)
+ spy.calls.reset()
+ timeline.schedule(runner, 0).play() // we have to play because its syncronous here
+ jasmine.RequestAnimationFrame.tick(1)
+ expect(spy).not.toHaveBeenCalled()
+ })
+
+ it('finishes if time is backwards and 0', () => {
+ const spy = createSpy()
+ const timeline = new Timeline().on('finished', spy).reverse(true)
+ const runner = new Runner(1000)
+ spy.calls.reset()
+ timeline.schedule(runner, 0).play() // we have to play because its syncronous here
+ jasmine.RequestAnimationFrame.tick(1)
+ expect(spy).toHaveBeenCalled()
+ })
+ })
+
+ describe('Element', () => {
+ describe('timeline()', () => {
+ it('sets and gets the timeline of the element', () => {
+ const timeline = new Timeline()
+ const rect = new Rect().timeline(timeline)
+ expect(rect.timeline()).toBe(timeline)
+ })
+
+ it('creates a timeline on the fly when getting it', () => {
+ expect(new Rect().timeline()).toEqual(any(Timeline))
+ })
+ })
+ })
})
+++ /dev/null
-/* globals describe, expect, it */
-
-import { easing } from '../../../src/main.js'
-
-describe('easing', () => {
- var easedValues = {
- '-': 0.5,
- '<>': 0.5,
- '>': 0.7071,
- '<': 0.2929
- }
-
- ;[ '-', '<>', '<', '>' ].forEach((el) => {
- describe(el, () => {
- it('is 0 at 0', () => {
- expect(easing[el](0)).toBe(0)
- })
- it('is 1 at 1', () => {
- expect(Math.round(easing[el](1) * 1000) / 1000).toBe(1) // we need to round cause for some reason at some point 1==0.999999999
- })
- it('is eased at 0.5', () => {
- expect(easing[el](0.5)).toBeCloseTo(easedValues[el])
- })
- })
- })
-})
function makeSetterGetter (k, f) {
return function (v) {
- if (v == null) return this[v]
+ if (v == null) return this[k]
this[k] = v
if (f) f.call(this)
return this
***/
export class Ease extends Stepper {
- constructor (fn) {
+ constructor (fn = timeline.ease) {
super()
- this.ease = easing[fn || timeline.ease] || fn
+ this.ease = easing[fn] || fn
}
step (from, to, pos) {
}
export class Spring extends Controller {
- constructor (duration, overshoot) {
+ constructor (duration = 500, overshoot = 0) {
super()
- this.duration(duration || 500)
- .overshoot(overshoot || 0)
+ this.duration(duration)
+ .overshoot(overshoot)
}
step (current, target, dt, c) {
})
export class PID extends Controller {
- constructor (p, i, d, windup) {
+ constructor (p = 0.1, i = 0.01, d = 0, windup = 1000) {
super()
-
- p = p == null ? 0.1 : p
- i = i == null ? 0.01 : i
- d = d == null ? 0 : d
- windup = windup == null ? 1000 : windup
this.p(p).i(i).d(d).windup(windup)
}
var p = target - current
var i = (c.integral || 0) + p * dt
var d = (p - (c.error || 0)) / dt
- var windup = this.windup
+ var windup = this._windup
// antiwindup
if (windup !== false) {
}
extend(PID, {
- windup: makeSetterGetter('windup'),
+ windup: makeSetterGetter('_windup'),
p: makeSetterGetter('P'),
i: makeSetterGetter('I'),
d: makeSetterGetter('D')
originY: 0
}
+const sortByKey = (a, b) => {
+ return (a[0] < b[0] ? -1 : (a[0] > b[0] ? 1 : 0))
+}
+
export class ObjectBag {
constructor (...args) {
this.init(...args)
entries.push([ i, objOrArr[i] ])
}
- entries.sort((a, b) => {
- return a[0] - b[0]
- })
+ entries.sort(sortByKey)
this.values = entries.reduce((last, curr) => last.concat(curr), [])
return this
push (value) {
// An item stores an id and the provided value
- var item = value.next ? value : { value: value, next: null, prev: null }
+ var item = typeof value.next !== 'undefined' ? value : { value: value, next: null, prev: null }
// Deal with the queue being empty or populated
if (this._last) {
} else if (when === 'now') {
absoluteStartTime = this._time
} else if (when === 'relative') {
- const runnerInfo = this._runners[runner.id]
+ const runnerInfo = this.getRunnerInfoById(runner.id)
if (runnerInfo) {
absoluteStartTime = runnerInfo.start + delay
delay = 0
}
+ } else if (when === 'with-last') {
+ const lastRunnerInfo = this.getLastRunnerInfo()
+ const lastStartTime = lastRunnerInfo ? lastRunnerInfo.start : this._time
+ absoluteStartTime = lastStartTime
} else {
throw new Error('Invalid value for the "when" parameter')
}
return this
}
+ getRunnerInfoById (id) {
+ return this._runners[this._runnerIds.indexOf(id)] || null
+ }
+
+ getLastRunnerInfo () {
+ return this.getRunnerInfoById(this._lastRunnerId)
+ }
+
// Calculates the end of the timeline
getEndTime () {
- var lastRunnerInfo = this._runners[this._runnerIds.indexOf(this._lastRunnerId)]
+ var lastRunnerInfo = this.getLastRunnerInfo()
var lastDuration = lastRunnerInfo ? lastRunnerInfo.runner.duration() : 0
- var lastStartTime = lastRunnerInfo ? lastRunnerInfo.start : 0
+ var lastStartTime = lastRunnerInfo ? lastRunnerInfo.start : this._time
return lastStartTime + lastDuration
}
getEndTimeOfTimeline () {
- let lastEndTime = 0
- for (var i = 0; i < this._runners.length; i++) {
- const runnerInfo = this._runners[i]
- var duration = runnerInfo ? runnerInfo.runner.duration() : 0
- var startTime = runnerInfo ? runnerInfo.start : 0
- const endTime = startTime + duration
- if (endTime > lastEndTime) {
- lastEndTime = endTime
- }
- }
- return lastEndTime
+ const endTimes = this._runners.map((i) => i.start + i.runner.duration())
+ return Math.max(0, ...endTimes)
}
// Makes sure, that after pausing the time doesn't jump
if (yes == null) return this.speed(-currentSpeed)
var positive = Math.abs(currentSpeed)
- return this.speed(yes ? positive : -positive)
+ return this.speed(yes ? -positive : positive)
}
seek (dt) {