Browse Source

Stuff is moving again after fixing several stuff

tags/3.0.0
Ulrich-Matthias Schäfer 6 years ago
parent
commit
acb8408fb9
6 changed files with 445 additions and 96 deletions
  1. 28
    28
      dirty.html
  2. 273
    0
      spec/spec/runner.js
  3. 12
    7
      src/controller.js
  4. 7
    2
      src/morph.js
  5. 30
    20
      src/runner.js
  6. 95
    39
      src/timeline.js

+ 28
- 28
dirty.html View File

@@ -46,34 +46,34 @@ function getColor(t) {



SVG('rect')
.clone().show()
.animate()
.move(200, 200)
// for (let i = 0 ; i < 15; i++) {
// for (let j = 0 ; j < 10; j++) {
//
// // Make the rect
// let o = i + j
// let rect = SVG('rect').clone().show()
// .width(40).height(40)
// .x(50 * i).y(50 * j)
// .attr('fill', getColor(o * 0.1))
//
// // Move the rect
// let {cx, cy} = rect.bbox()
//
// // Animate the rect
// rect.animate(3000, Math.random() * 2000)
// .during(t => rect.transform({rotate: 720 * t, origin: [cx, cy]}))
// .during(t => rect.attr('fill', getColor(o * 0.1 + t)))
// }
// }
// SVG('rect')
// .clone().show()
// .animate()
// .move(200, 200)
for (let i = 0 ; i < 15; i++) {
for (let j = 0 ; j < 10; j++) {
// Make the rect
let o = i + j
let rect = SVG('rect').clone().show()
.width(40).height(40)
.x(50 * i).y(50 * j)
.attr('fill', getColor(o * 0.1))
// Move the rect
let {cx, cy} = rect.bbox()
// Animate the rect
rect.animate(3000, Math.random() * 2000)
.during(t => rect.transform({rotate: 720 * t, origin: [cx, cy]}))
.during(t => rect.attr('fill', getColor(o * 0.1 + t)))
}
}


</script>

+ 273
- 0
spec/spec/runner.js View File

@@ -0,0 +1,273 @@
describe('SVG.Runner', function () {

var initFn = jasmine.createSpy('initFn')
var runFn = jasmine.createSpy('runFn')

describe('())', function () {
it('creates a runner with defaults', function () {
var runner = new SVG.Runner()
expect(runner instanceof SVG.Runner).toBe(true)
expect(runner._duration).toBe(SVG.timeline.duration)
expect(runner._ease).toBe(SVG.timeline.ease)
expect(runner._stepper).toBe(null)
})

it('creates a runner with duration set', function () {
var runner = new SVG.Runner(1000)
expect(runner instanceof SVG.Runner).toBe(true)
expect(runner._duration).toBe(1000)
expect(runner._ease).toBe(SVG.timeline.ease)
expect(runner._stepper).toBe(null)
})

it('creates a runner with controller set', function () {
var runner = new SVG.Runner(runFn)
expect(runner instanceof SVG.Runner).toBe(true)
expect(runner._duration).toBe(null)
expect(runner._ease).toBe(SVG.timeline.ease)
expect(runner._stepper).toBe(runFn)
})
})

describe('constructors', function () {
describe('animate()', function () {
it('creates a runner with the element set and schedules it on the timeline', function () {
spyOn(SVG, 'Runner').and.callTrough()

var element = SVG('<rect>')
var runner = element.animate()
expect(SVG.Runner).toHaveBeenCalled();
expect(runner instanceof SVG.Runner)
expect(runner.element()).toBe(element)
expect(element.timeline()._runners.length).toBe(1)
})
})

describe('loop()', function () {
it('calls animate with correct parameters', function () {
var element = SVG('<rect>')

spyOn(element, 'animate')
element.loop()
expect(element.animate).toHaveBeenCalledWith(objectContaining({
duration: SVG.defaults.timeline.duration,
times: Infinity,
swing: false
}));
})
})

describe('delay()', function () {
it('calls animate with correct parameters', function () {
var element = SVG('<rect>')

spyOn(element, 'animate')
element.delay(100, 'now')
expect(element.animate).toHaveBeenCalledWith(0, 100, 'now')
})
})
})

describe('queue()', function () {
it('adds another closure to the runner', function () {
var runner = new SVG.Runner()
runner.queue(initFn, runFn, true)

expect(runner.functions[0]).toEqual(jasmine.objectContaining({
alwaysInitialise: true,
initialiser: initFn,
runner: runFn
}))
})
})

describe('tag()', function () {

it('acts as a getter', function () {
var runner = new SVG.Runner()

runner.tags = {foo: true}
expect(runner.tag()).toEqual(jasmine.arrayContaining(['foo']))
})

it('sets one tag with a string given', function () {
var runner = new SVG.Runner()

runner.tag('foo')
expect(runner.tags).toEqual(jasmine.objectContaining({foo: true}))
})

it('sets multiple tags with an array given', function () {
var runner = new SVG.Runner()

runner.tag(['foo', 'bar', 'baz'])
expect(runner.tags).toEqual(jasmine.objectContaining({foo: true, bar: true, baz: true}))
})
})

describe('step()', function () {
it('calls initFn once and runFn at every step when alwaysInitialise is false', function() {
var runner = new SVG.Runner()
runner.queue(initFn, runFn, false)

runner.step()
expect(initFn).toHaveBeenCalled()
expect(runFn).toHaveBeenCalled()

runner.step()
expect(init.calls.count()).toBe(1)
expect(runFn.calls.count()).toBe(2)
})

it('calls initFn and runFn at every step when alwaysInitialise is true', function() {
var runner = new SVG.Runner()
runner.queue(initFn, runFn, true)

runner.step()
expect(initFn).toHaveBeenCalled()
expect(runFn).toHaveBeenCalled()

runner.step()
expect(initFn.calls.count()).toBe(2)
expect(runFn.calls.count()).toBe(2)
})

it('returns false if not finished', function () {
var runner = new SVG.Runner()
runner.queue(initFn, runFn, false)

expect(runner.step()).toBe(false)
})

it('returns true if finished', function () {
var runner = new SVG.Runner()
runner.queue(initFn, runFn, false)

expect(runner.step()).toBe(true)
})
})

describe('active()', function () {
it('acts as a getter without parameters', function () {
var runner = new SVG.Runner()
expect(runner.active()).toBe(true)
})

it('disabled the runner when false is passed', function () {
var runner = new SVG.Runner()
expect(runner.active(false)).toBe(runner)
expect(runner.active()).toBe(false)
})

it('enables the runner when true is passed', function () {
var runner = new SVG.Runner()
expect(runner.active(false)).toBe(runner)
expect(runner.active(true)).toBe(runner)
expect(runner.active()).toBe(true)
})
})

describe('time()', function () {
it('returns itself', function () {
var runner = new SVG.Runner()
expect(runner.time(0)).toBe(runner)
})

it('acts as a getter with no parameter passed', function () {
var runner = new SVG.Runner()
expect(runner.time()).toBe(0)
})

it('reschedules the runner to a new time', function () {
var runner = new SVG.Runner()
runner.time(10)

expect(runner.time()).toBe(10)
})

it('calls step to reschedule', function () {
var runner = new SVG.Runner()
spyOn(runner, 'step')
runner.time(10)

expect(runner.step).toHaveBeenCalledWith(10)
})
})

describe('element()', function () {
it('returns the element bound to this runner if any', function () {
var runner1 = new SVG.Runner()
expect(runner1.element()).toBe(null)

var element = SVG('<rect>')
var runner2 = element.animate()
expect(runner1.element()).toBe(element)
})

it('sets an element to be bound to the runner', function () {
var runner = new SVG.Runner()
var element = SVG('<rect>')
runner.element(element)
expect(runner.element()).toBe(element)
})
})

describe('during()', function () {
it('returns itself', function () {
var runner = new SVG.Runner()
expect(runner.during(runFn)).toBe(runner)
})

it('calls queue giving only a function to call on every step', function () {
var runner = new SVG.Runner()
spyOn(runner.queue)
runner.during(runFn)

expect(runner.queue).toHaveBeenCalledWith(null, runFn)
})
})

describe('finish()', function () {
it('returns itself', function () {
var runner = new SVG.Runner()
expect(runner.finish()).toBe(runner)
})

it('calls step with Infinity as arument', function () {
var runner = new SVG.Runner()
spyOn(runner.step)
runner.finish()

expect(runner.step).toHaveBeenCalledWith(Infinity)
})
})

describe('reverse()', function () {
it('returns itself', function () {
var runner = new SVG.Runner()
expect(runner.reverse()).toBe(runner)
})

it('reverses the runner by setting the time to the end and going backwards', function () {
var runner = new SVG.Runner()
spyOn(runner.time)
runner.reverse()

expect(runner.time).toHaveBeenCalledWith(SVG.defaults.timeline.duration)
})
})

describe('ease()', function () {
it('returns itself', function () {
var runner = new SVG.Runner()
expect(runner.ease(function () {})).toBe(runner)
})

it('creates an easing Controller from the easing function', function () {
var runner = new SVG.Runner()
runner.ease(function () {})

expect(runner._stepper instanceof SVG.Stepper).toBe(true)
})
})
})

+ 12
- 7
src/controller.js View File

@@ -9,7 +9,7 @@ Base Class
The base stepper class that will be
***/

SVG.Stepper = SVG.Invent ({
SVG.Stepper = SVG.invent ({

create: function (fn) {

@@ -32,22 +32,27 @@ Easing Functions
================
***/

SVG.Ease = SVG.Invent ({
SVG.Ease = SVG.invent ({

inherit: SVG.Stepper,

create: function (fn) {
SVG.Stepper.call(this, fn)

this.ease = SVG.easing[fn || SVG.defaults.timeline.ease] || fn
},

extend: {

step: function (current, target, dt, c) {

step: function (from, to, pos) {
if(typeof from !== 'number') {
return pos < 1 ? from : to
}
return from + (to - from) * this.ease(pos)
},

isComplete: function (dt, c) {
return false
},
},
})
@@ -70,7 +75,7 @@ Controller Types
================
***/

SVG.Controller = SVG.Invent ({
SVG.Controller = SVG.invent ({

inherit: SVG.Stepper,

@@ -107,7 +112,7 @@ SVG.Spring = function spring(duration, overshoot) {
var K = wn * wn

// Return the acceleration required
return SVG.Controller(
return new SVG.Controller(
function (current, target, dt, c) {

if(dt == Infinity) return target

+ 7
- 2
src/morph.js View File

@@ -94,6 +94,11 @@ SVG.Morphable = SVG.invent({
this._stepper = stepper
},

// FIXME: we can call this._stepper.isComplete directly
// no need for this wrapper here
isComplete: function () {
return this._stepper && this._stepper.isComplete()
},

at: function (pos) {
var _this = this
@@ -105,11 +110,11 @@ SVG.Morphable = SVG.invent({
return this._type.prototype.fromArray(
this.modifier(
this._from.map(function (i, index) {
return _this._stepper.step(i, _this._to[index], pos, _this.context[i])
return _this._stepper.step(i, _this._to[index], pos, _this._context[i])
})
)
)
}
},

valueOf: function () {
return this._value

+ 30
- 20
src/runner.js View File

@@ -6,23 +6,25 @@ SVG.easing = {
'<': function (pos) { return -Math.cos(pos * Math.PI / 2) + 1 }
}

SVG.Runner = SVG.Invent({
SVG.Runner = SVG.invent({

parent: SVG.Element,

create: function (options) {

options = options || SVG.defaults.timeline.duration

// Declare all of the variables
this._element = null
this._functions = []
this.done = false

// Work out the stepper and the duration
this._stepper = typeof options === 'function' && (options)
this._duration = typeof options === 'number' && options
this._isDeclaritive = options instanceof SVG.Controller
this._stepper = this._isDeclaritive ? options : new SVG.Ease()

// We copy the current values from the timeline because they can change
this._ease = SVG.defaults.timeline.ease
this._morphers = {}

// Store the state of the runner
@@ -51,9 +53,11 @@ SVG.Runner = SVG.Invent({
waits = duration.waits || []
}

// FIXME: take care of looping here because loop is a constructor
// alternatively disallow loop as constructor
// Construct a new runner and setup its looping behaviour
var runner = new SVG.Runner(duration)
.loop(times, swing, waits)
//.loop(times, swing, waits)
.element(this)

// Attach this animation to a timeline
@@ -95,6 +99,12 @@ SVG.Runner = SVG.Invent({
return this
},

timeline: function () {
return this._element.timeline()
},

// FIXME: It makes totally sense to call this, when the runner is attached to a timeline.
// So maybe we should attach runners to timelines and timelines to elements instead of the other way round
animate: function () {
if(this._element) {
return this._element.animate.apply(this._element, arguments)
@@ -196,10 +206,12 @@ SVG.Runner = SVG.Invent({

// Changes the animation easing function
ease: function (fn) {

this._stepper = SVG.Ease(fn)
return this
},

enable: function (enabled) {
active: function (enabled) {
if(active == null) return this._enabled
this._enabled = enabled
return this
},
@@ -307,15 +319,12 @@ SVG.extend(SVG.Runner, {

var morpher = new Morphable(this._stepper).to(val)

this.queue(
function () {
morpher = morpher.from(this[type](name))
},
function () {
this[type](name, morpher.at(pos))
return morpher.isComplete()
}
)
this.queue(function () {
morpher = morpher.from(this[type](name))
}, function () {
this[type](name, morpher.at(pos))
return morpher.isComplete()
}, this._isDeclarative)

return this
},
@@ -328,7 +337,7 @@ SVG.extend(SVG.Runner, {
}, function (pos) {
this.zoom(morpher.at(pos), point)
return morpher.isComplete()
})
}, this._isDeclarative)

return this
},
@@ -378,7 +387,8 @@ SVG.extend(SVG.Runner, {

return this.queue(function() {}, function (pos) {
this.pushRightTransform(new Matrix(morpher.at(pos)))
})
return morpher.isComplete()
}, this._isDeclarative)
}


@@ -420,7 +430,7 @@ SVG.extend(SVG.Runner, {
}

return morpher.isComplete()
})
}, this._isDeclarative)

return this
},
@@ -458,7 +468,7 @@ SVG.extend(SVG.Runner, {
}, function (pos) {
this[method](morpher.at(pos))
return morpher.isComplete()
}, this._declarative)
}, this._isDeclarative)

// Register the morpher so that if it is changed again, we can retarget it
this._saveMorpher(method, morpher)
@@ -477,7 +487,7 @@ SVG.extend(SVG.Runner, {
}, function (pos) {
this[method](morpher.at(pos))
return morpher.isComplete()
}, this._declarative)
}, this._isDeclarative)

// Register the morpher so that if it is changed again, we can retarget it
this._saveMorpher(method, morpher)

+ 95
- 39
src/timeline.js View File

@@ -124,21 +124,74 @@ SVG.Timeline = SVG.invent({
} else {
return this._runner
}
}
},

schedule (runner, delay, when) {

// 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

schedule () {
// Work out when to start the animation
if ( when == null || when === 'last' || when === 'relative' ) {
// 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' ) {
} else if (when === '' ) {
absoluteStartTime = delay
} else if (when === 'now') {
absoluteStartTime = this._time + delay
} else {
// TODO: Throw error
}
}

runner.time(absoluteStartTime)
this._startTime = absoluteStartTime + runner._duration
this._runners.push(runner)

this._step()

return this



// 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
},

play () {

@@ -159,23 +212,27 @@ SVG.Timeline = SVG.invent({
stop () {
// Cancel the next animation frame for this object
this._nextFrame = null
return this
},

finish () {
return this
},

speed (newSpeed) {
this._speed = newSpeed
return this
},

seek (dt) {
this._time += dt
return this
},

time (t) {
this._time = t
}
return this
},

persist (dtOrForever) {
// 0 by default
@@ -266,43 +323,42 @@ SVG.Timeline = SVG.invent({
},
},

// Only elements are animatable
parent: SVG.Element,

// These methods will be added to all SVG.Element objects
parent: SVG.Element,
construct: {

timeline: function () {
this.timeline = (this.timeline || new SVG.Timeline(this))
return this.timeline
},

animate: function(o, delay, now) {

// Get the current timeline or construct a new one
this.timeline = (this.timeline || new SVG.Timeline(this))
.animate(o, delay, now)
this.timeline._loops = null
return this.timeline
this._timeline = (this._timeline || new SVG.Timeline(this))
return this._timeline
},

loop: function(o) {

/*
{
swing: wether or not the animation should repeat when its done
times: the number of times to loop the animation
wait: [array] a buffer of times to wait between successive animations
delay: defaults.timeline to wait
}
*/
this.timeline = (this.timeline || new SVG.Timeline(this))

// REFACTOR this into an init function
this.timeline._waits = [].concat(o.wait || o.delay || 0)
this.timeline._loops = o.times || Infinity
this.timeline._swing = o.swing || false
return this.timeline
}
// animate: function(o, delay, now) {
//
// // Get the current timeline or construct a new one
// this.timeline = (this.timeline || new SVG.Timeline(this))
// .animate(o, delay, now)
// this.timeline._loops = null
// return this.timeline
// },
//
// loop: function(o) {
//
// /*
// {
// swing: wether or not the animation should repeat when its done
// times: the number of times to loop the animation
// wait: [array] a buffer of times to wait between successive animations
// delay: defaults.timeline to wait
// }
// */
// this.timeline = (this.timeline || new SVG.Timeline(this))
//
// // REFACTOR this into an init function
// this.timeline._waits = [].concat(o.wait || o.delay || 0)
// this.timeline._loops = o.times || Infinity
// this.timeline._swing = o.swing || false
// return this.timeline
// }
}
})

Loading…
Cancel
Save