summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorUlrich-Matthias Schäfer <ulima.ums@googlemail.com>2015-12-20 15:43:40 +0100
committerUlrich-Matthias Schäfer <ulima.ums@googlemail.com>2015-12-20 15:43:40 +0100
commitb40238cdffb15259196c3a9be5c477418a111542 (patch)
tree1da7a47552db6b3d9b6d91fe1d6469a17ec349e2 /src
parent5cb2010246154e3a5dfa753f14fb081cef5c2579 (diff)
downloadsvg.js-b40238cdffb15259196c3a9be5c477418a111542.tar.gz
svg.js-b40238cdffb15259196c3a9be5c477418a111542.zip
new specs, reverse, initAnimation, after, during, comments
Diffstat (limited to 'src')
-rw-r--r--src/fxnew.js564
1 files changed, 264 insertions, 300 deletions
diff --git a/src/fxnew.js b/src/fxnew.js
index 377bde9..2960845 100644
--- a/src/fxnew.js
+++ b/src/fxnew.js
@@ -5,6 +5,8 @@ SVG.easing = {
, '<': function(pos){return -Math.cos(pos * Math.PI / 2) + 1}
}
+var someVar = 0
+
SVG.FX = SVG.invent({
create: function(element) {
@@ -19,18 +21,20 @@ SVG.FX = SVG.invent({
this._next = null
this._prev = null
+ this.id = someVar++
+
this.animations = {
// functionToCall: [morphable object, destination value]
// e.g. x: [SVG.Number, 5]
// this way its assured, that the start value is set correctly
}
-
+
this.attrs = {
// todo: check if attr is present in animation before saving
}
-
+
this.styles = {
-
+
}
this._once = {
@@ -42,28 +46,38 @@ SVG.FX = SVG.invent({
, extend: {
+ // sets up the animation
animate: function(o){
o = o || {}
+ if(typeof o == 'number') o = {duration:o}
+
this._duration = o.duration || 1000
this._delay = o.delay || 0
- this._start = +new Date + this._delay
+
+ // the end time of the previous is our start
+ var start = this._prev ? this._prev._end : +new Date
+
+ this._start = start + this._delay
this._end = this._start + this._duration
this.easing = SVG.easing[o.easing || '-'] || o.easing // when easing is a function, its not in SVG.easing
+ this.init = false
+
return this
}
+ // adds a new fx obj to the animation chain
, enqueue: function(o){
// create new istance from o or use it directly
return this.next(
o instanceof SVG.FX ? o :
- new SVG.FX(this.target).animate(o)
- ).next().share(this.shared)
+ new SVG.FX(this.target)
+ ).next().share(this.shared).animate(o)
}
- // return the next situation object in the animation queue
+ // sets or gets the next situation object in the animation queue
, next: function(next){
if(!next) return this._next
@@ -72,6 +86,7 @@ SVG.FX = SVG.invent({
return this
}
+ // sets or gets the previous situation object in the animation queue
, prev: function(prev){
if(!prev) return this._prev
@@ -80,26 +95,25 @@ SVG.FX = SVG.invent({
return this
}
+ // returns the first situation object...
, first: function(){
var prev = this
while(prev.prev()){
- pref = prev.prev()
+ prev = prev.prev()
}
-
return prev
}
+ // returns the last situation object...
, last: function(){
-
var next = this
while(next.next()){
next = next.next()
}
-
return next
-
}
+ // sets the shared object which is just a shared reference between all objects
, share: function(shared){
this.shared = shared
return this
@@ -115,30 +129,32 @@ SVG.FX = SVG.invent({
return this._duration * pos + this._start
}
+ // starts the animationloop
+ // TODO: It may be enough to call just this.step()
, startAnimFrame: function(){
this.animationFrame = requestAnimationFrame(function(){ this.step() }.bind(this))
}
+ // cancels the animationframe
+ // TODO: remove this in favour of the oneliner
, stopAnimFrame: function(){
cancelAnimationFrame(this.animationFrame)
}
+ // returns the current (active) fx object
, current: function(){
return this.shared.current
}
- , start: function(){
-
- /*if(this.fx().current() == this){
- // morph values from the current position to the destination - maybe move this to another place
- for(var i in this.animations){
- if(this.animations[i] instanceof Array){
+ // set this object as current
+ , setAsCurrent: function(){
+ this.shared.current = this
+ return this
+ }
- this.animations[i] = new this.animations[i][0](this.fx().target[i]()).morph(this.animations[i][1])
- console.log(i, this.animations[i])
- }
- }
- }*/
+ // kicks off the animation - only does something when this obejct is the current
+ // todo: remove timeout. we dont rly need a delay. it can be accomplished from the user itself
+ , start: function(){
// dont start if already started
if(!this.active && this.current() == this){
@@ -146,35 +162,71 @@ SVG.FX = SVG.invent({
this._end = this._start + this._duration
this.active = true
+ this.init || this.initAnimations()
+
this.timeout = setTimeout(function(){ this.startAnimFrame() }.bind(this), this.delay)
}
return this
}
+ // updates all animations to the current state of the element
+ // this is important when one property could be changed from another property
+ , initAnimations: function() {
+ var i
+
+ for(i in this.animations){
+ // TODO: this is not a clean clone of the array. We may have some unchecked references
+ this.animations[i].value = (i == 'plot' ? this.target.array().value : this.target[i]())
+ }
+
+ for(i in this.attrs){
+ this.attrs[i].value = this.target.attr(i)
+ }
+
+ for(i in this.styles){
+ this.styles[i].value = this.target.style(i)
+ }
+
+ this.init = true
+ }
+
+ // resets the animation to the initial state
+ // TODO: maybe rename to reset
, stop: function(){
if(!this.active) return false
this.active = false
this.stopAnimFrame()
clearTimeout(this.timeout)
- return this
+ return this.seek(0)
}
+ // finish off the animation
+ // TODO: does it kickoff the next animation in the queue?
+ // global finish or fx specific finish?
+ , finish: function(){
+ this.finished = true
+ return this.stop().seek(1)
+ }
+
+ // set the internal animation pointer to the specified position and updates the visualisation
, seek: function(pos){
this.pos = pos
- this._start = -pos * this.duration + new Date
+ this._start = +new Date - pos * this._duration
this._end = this._start + this._duration
- return this
+ return this.step(true)
}
+ // speeds up the animation by the given factor
+ // this changes the duration of the animation
, speed: function(speed){
- this.speed = speed
- this.duration = this.duration * this.pos + (1-this.pos) * this.duration / speed
+ this._duration = this._duration * this.pos + (1-this.pos) * this._duration / speed
this._end = this._start + this._duration
- return this
+ return this.seek(this.pos)
}
+ // pauses the animation
, pause: function(){
this.paused = true
this.stopAnimFrame()
@@ -182,79 +234,223 @@ SVG.FX = SVG.invent({
return this
}
+ // sets the direction to forward
, play: function(){
- if(this.paused){
+ if(this.shared.reversed){
+ this.shared.reversed = false
this.seek(this.pos)
+ }
+
+ return this
+ }
+
+ // sets the direction to backwards
+ , reverse: function(){
+ if(!this.shared.reversed){
+ this.shared.reversed = true
+ this.seek(1-this.pos)
+ }
+ return this
+ }
+
+ // resumes a currently paused animation
+ , resume: function(){
+ if(this.paused){
+ this.seek(this.shared.reversed ? 1-this.pos : this.pos)
this.paused = false
this.startAnimFrame()
}
+ return this
+ }
+ // adds a callback function for the current animation which is called when this animation finished
+ , after: function(fn){
+ var _this = this
+ , wrapper = function wrapper(e){
+ if(e.detail.fx == _this){
+ fn.call(this)
+ this.off('finished.fx', wrapper) // prevent memory leak
+ }
+ }
+
+ // unbind previously set bindings because they would be overwritten anyway
+ this.target.off('finished.fx', wrapper).on('finished.fx', wrapper)
return this
}
+ // adds a callback which is called whenever one animation step is performed
+ , during: function(fn){
+ var _this = this
+ , wrapper = function(e){
+ if(e.detail.fx == _this){
+ fn.call(this, e.detail.pos, e.detail.eased, e.detail.fx)
+ }
+ }
+
+ // see above
+ this.target.off('during.fx', wrapper).on('during.fx', wrapper)
+
+ return this.after(function(){
+ this.off('during.fx', wrapper)
+ })
+ }
+
+ // calls after ALL animations in the queue are finished
+ , afterAll: function(fn){
+ var wrapper = function wrapper(e){
+ fn.call(this)
+ this.off('allfinished.fx', wrapper)
+ }
+
+ // see above
+ this.target.off('allfinished.fx', wrapper).on('allfinished.fx', wrapper)
+ return this
+ }
+
+ // calls on every animation step for all animations
+ , duringAll: function(fn){
+ var _this = this
+ , wrapper = function(e){
+ fn.call(this, e.detail.fx.totalPosition(), e.detail.pos, e.detail.eased, e.detail.fx)
+ }
+
+ this.target.off('during.fx', wrapper).on('during.fx', wrapper)
+
+ return this.afterAll(function(){
+ this.off('during.fx', wrapper)
+ })
+ }
+
+ // returns an integer from 0-1 indicating the progress of the whole animation queue
+ // we recalculate the end time because it may be changed from methods like seek()
+ // todo: rename position to progress?
+ , totalPosition: function(){
+ var start = this.first()._start
+ , end = this._end
+ , next = this
+
+ while(next = next.next()){
+ end += next._duration + next._delay
+ }
+
+ return (this.pos * this._duration + this._start - start) / (end - start)
+ }
+
+ // adds one property to the animations
, push: function(method, args, type){
this[type || 'animations'][method] = args
return this.start()
}
+ // removes the specified animation and returns it
, pop: function(method, type){
var ret = this[type || 'animations'][method]
this.drop(method)
return ret
}
+ // removes the specified animation
, drop: function(method, type){
delete this[type || 'animations'][method]
return this
}
+ // returns the specified animation
, get: function(method, type){
return this[type || 'animations'][method]
}
- , step: function(){
-
- if(this.paused) return this
+ // perform one step of the animation
+ // when ignoreTime is set the method uses the currently set position.
+ // Otherwise it will calculate the position based on the time passed
+ , step: function(ignoreTime){
- this.pos = this.timeToPos(+new Date)
+ // convert current time to position
+ if(!ignoreTime) this.pos = this.timeToPos(+new Date)
+ if(this.shared.reversed) this.pos = 1 - this.pos
+
+ // correct position
if(this.pos > 1) this.pos = 1
if(this.pos < 0) this.pos = 0
+ // apply easing
var eased = this.easing(this.pos)
+ // call once-callbacks
for(var i in this._once){
- if(i > this.lastPos && i <= eased) this._once[i](this.pos, eased)
+ if(i > this.lastPos && i <= eased){
+ this._once[i](this.pos, eased)
+ delete this._once[i]
+ }
}
- this.target.fire('during', {pos: this.pos, eased: eased})
+ // fire during callback with position, eased position and current situation as parameter
+ this.target.fire('during', {pos: this.pos, eased: eased, fx: this})
+ // apply the actual animation to every property
this.eachAt(function(method, args){
this.target[method].apply(this.target, args)
})
+ // do final code when situation is finished
if(this.pos == 1){
+
+ // stop animation callback
+ cancelAnimationFrame(this.animationFrame)
+
this.finished = true
this.active = false
- this.target.fire('situationfinished')
- if(this == this.last()) this.target.fire('fxfinished')
-
- if(this.next())(this.shared.current = this.next()).start()
+ // fire finished callback with current situation as parameter
+ this.target.fire('finished', {fx:this})
+
+ // start the next animation in the queue and mark it as current
+ if(this.next()){
+ this.next().setAsCurrent().start()
+ // or finish off the animation
+ }else{
+ this.target.fire('allfinished')
+ this.target.off('.fx')
+ this.target.fx = null
+ }
+ // todo: this is more or less duplicate code. has to be removed
+ }else if(this.shared.reversed && this.pos == 0){
+ // stop animation callback
cancelAnimationFrame(this.animationFrame)
- }else{
+
+ this.finished = true
+ this.active = false
+
+ // fire finished callback with current situation as parameter
+ this.target.fire('finished', {fx:this})
+
+ // start the next animation in the queue and mark it as current
+ if(this.prev()){
+ this.prev().setAsCurrent().start()
+ // or finish off the animation
+ }else{
+ this.target.fire('allfinished')
+ this.target.off('.fx')
+ this.target.fx = null
+ }
+ }else if(!this.paused && this.active){
+ // we continue animating when we are not at the end
this.startAnimFrame()
}
+ // save last eased position for once callback triggering
this.lastPos = eased
return this
}
+ // calculates the step for every property and calls block with it
+ // todo: include block directly cause it is used only for this purpose
, eachAt: function(block){
var i, at
-
+
for(i in this.animations){
at = [].concat(this.animations[i]).map(function(el){
@@ -265,7 +461,7 @@ SVG.FX = SVG.invent({
block.call(this, i, at)
}
-
+
for(i in this.attrs){
at = [i].concat(this.attrs[i]).map(function(el){
@@ -276,7 +472,7 @@ SVG.FX = SVG.invent({
block.call(this, 'attr', at)
}
-
+
for(i in this.styles){
at = [i].concat(this.styles[i]).map(function(el){
@@ -287,12 +483,13 @@ SVG.FX = SVG.invent({
block.call(this, 'style', at)
}
-
+
return this
}
+ // adds an once-callback which is called at a specific position and never again
, once: function(pos, fn, isEased){
if(!isEased)pos = this.easing(pos)
@@ -302,7 +499,8 @@ SVG.FX = SVG.invent({
return this
}
- // with the help of key this function can be used to retrieve
+ // searchs for a property in the animation chain to make relative movement possible
+ // TODO: this method is outdated because of the use of initAnimations which cover this topic quite well
, search: function(method, key) {
var situation = this
@@ -339,7 +537,7 @@ SVG.FX = SVG.invent({
})
-
+// MorphObj is used whenever no morphable object is given
SVG.MorphObj = SVG.invent({
create: function(from, to){
@@ -349,13 +547,17 @@ SVG.MorphObj = SVG.invent({
if(SVG.regex.unit.test(to) || typeof from == 'number') return new SVG.Number(from).morph(to)
// prepare for plain morphing
- this.from = from
+ this.value = from
this.destination = to
}
-
+
, extend: {
at: function(pos, real){
- return real < 1 ? this.from : this.destination
+ return real < 1 ? this.value : this.destination
+ },
+
+ valueOf: function(){
+ return this.value
}
}
@@ -368,7 +570,7 @@ SVG.extend(SVG.FX, {
if (typeof a == 'object') {
for (var key in a)
this.attr(key, a[key])
-
+
} else {
// get the current state
var from = this.search('attr', a)
@@ -381,7 +583,7 @@ SVG.extend(SVG.FX, {
// prepare matrix for morphing
this.push(a, (new SVG.Matrix(this.target)).morph(v), 'attrs')
-
+
// add parametric rotation values
/*if (this.param) {
// get initial rotation
@@ -398,11 +600,11 @@ SVG.extend(SVG.FX, {
if(typeof this[a] == 'function'){
return this[a](v)
}
-
+
this.push(a, new SVG.MorphObj(from, v), 'attrs')
}
}
-
+
return this
}
// Add animatable styles
@@ -410,10 +612,10 @@ SVG.extend(SVG.FX, {
if (typeof s == 'object')
for (var key in s)
this.style(key, s[key])
-
+
else
this.push(s, new SVG.MorphObj(this.search('style', s), v), 'styles')
-
+
return this
}
// Animatable x-axis
@@ -445,13 +647,13 @@ SVG.extend(SVG.FX, {
if (this.target instanceof SVG.Text) {
// animate font size for Text elements
this.attr('font-size', width)
-
+
} else {
// animate bbox based size for all other elements
var w = this.search('width')
, h = this.search('height')
, box
-
+
if(!w || !h){
box = this.target.bbox()
}
@@ -460,7 +662,7 @@ SVG.extend(SVG.FX, {
.push('height', new SVG.Number(h || box.height).morph(height))
}
-
+
return this
}
// Add animatable plot
@@ -469,7 +671,7 @@ SVG.extend(SVG.FX, {
}
// Add leading method
, leading: function(value) {
- return this.target.leading ?
+ return this.target.leading ?
this.push('leading', new SVG.Number(this.search('leading')).morph(value)) :
this
}
@@ -484,245 +686,7 @@ SVG.extend(SVG.FX, {
new SVG.Number(box.height).morph(height)
])
}
-
- return this
- }
-})
-
-/*
-SVG.FX = SVG.invent({
- // Initialize FX object
- create: function(element) {
- // store target element
- this.target = element
- this._queue = []
- this._current = 0
- }
-
- // Add class methods
-, extend: {
-
- // pushs a new situation to the queue
- enqueue: function(o) {
- this.queue().push(new SVG.Situation(o).fx(this))
- return this
- }
-
- // returns the queue
- , queue: function() {
- return this._queue;
- }
-
- , current: function() {
- return this.get(this._current)
- }
-
- , last: function() {
- return this.get(this.queue().length-1)
- }
-
- , first: function() {
- return this.get(0)
- }
-
- , search: function(attr) {
- var current = this.queue().length-1
-
- while(situation = this.get(--current)){
-
- // get method of situation if present
- var attr = situation.get(attr)
- if(!attr) continue
-
- // if not yet morphed we extract the destination from the array
- //if(attr instanceof Array) return attr[1]
-
- // otherwise from the morphed object
- return attr.destination
-
- }
-
- // return the elements attribute as fallback
- return this.target[attr]()
-
- }
-
- , prev: function() {
- return this.get(--this._current)
- }
-
- , next: function() {
- return this.get(++this._current)
- }
-
- , get: function(i) {
- if(!this._queue[i]) return null
- return this._queue[i]
- }
-
- , startNext: function() {
-
- var next = this.next()
- if(next) next.start()
- else this.finish()
-
- return this
- }
-
- , pause: function() {
- this.current().pause()
- return this
- }
-
- , play: function() {
- this.current().play()
- return this
- }
-
- , resume: function() {
- this.active = true
- }
-
- , finish: function() {
- this.active = false
- this.target.fire('fxfinished')
- return this
- }
-
- , reverse: function() {
- this.reverse = true
- this.current().play()
- return this
- }
-
- , progress: function(pos) {
- this.get(this._current).progress(pos)
- return this
- }
-
- , totalProgress: function(pos) {
- if(pos == null) return this.total
- this.total = pos
- return this
- }
-
- , time: function(d) {
- return this.progress(this.duration / d)
- }
-
- , totalTime: function(d) {
- return this.totalProgress(this.duration / d)
- }
-
- , timeScale: function(factor) {
- this.scale = factor
- return this
- }
-
- // Animatable x-axis
- , x: function(x) {
- //this.last().push('x', [SVG.Number, x]).start()
- this.last().push('x', new SVG.Number(this.search('x')).morph(x)).start()
- return this
- }
- // Animatable y-axis
- , y: function(y) {
- //this.last().push('y', [SVG.Number, y]).start()
- this.last().push('y', new SVG.Number(this.search('y')).morph(y)).start()
-
- return this
- }
- // Animatable center x-axis
- , cx: function(x) {
- //this.last().push('cx', [SVG.Number, x]).start()
- this.last().push('cx', new SVG.Number(this.search('cx')).morph(x)).start()
-
- return this
- }
- // Animatable center y-axis
- , cy: function(y) {
- //this.last().push('cy', [SVG.Number, y]).start()
- this.last().push('cy', new SVG.Number(this.search('cy')).morph(y)).start()
-
- return this
- }
- // 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)
- }
- , dx: function(x) {
- return this.x(this.search('x') + x)
- }
- , dy: function(y) {
- return this.y(this.search('y') + y)
- }
- // Relative move over x and y axes
- , dmove: function(x, y) {
- return this.dx(x).dy(y)
- }
- , attr: function(a, v) {
- // apply attributes individually
- if (typeof a == 'object') {
- for (var key in a)
- this.attr(key, a[key])
- } else {
- // get the current state
- //var from = this.target.attr(a)
-
- // detect format
- if (a == 'transform') {
- // merge given transformation with an existing one
- if (this.attrs[a])
- v = this.attrs[a].destination.multiply(v)
-
- // prepare matrix for morphing
- this.attrs[a] = (new SVG.Matrix(this.target)).morph(v)
-
- // add parametric rotation values
- if (this.param) {
- // get initial rotation
- v = this.target.transform('rotation')
-
- // add param
- this.attrs[a].param = {
- from: this.target.param || { rotation: v, cx: this.param.cx, cy: this.param.cy }
- , to: this.param
- }
- }
-
- } else {
- this.attrs[a] = SVG.Color.isColor(v) ?
- // prepare color for morphing
- new SVG.Color(from).morph(v) :
- SVG.regex.unit.test(v) ?
- // prepare number for morphing
- new SVG.Number(from).morph(v) :
- // prepare for plain morphing
- { from: from, to: v }
- }
- }
-
- return this
- }
-
- }
-
- // Define parent class
-, parent: SVG.Element
-
- // Add method to parent elements
-, construct: {
- // Get fx module or create a new one, then animate with given duration and ease
- animate: function(o) {
- return (this.fx || (this.fx = new SVG.Situation(this))).animate(o)
- }
- , delay: function(delay){
- return (this.fx || (this.fx = new SVG.Situation(this))).animate({delay:delay})
- }
+ return this
}
-})*/ \ No newline at end of file
+}) \ No newline at end of file