Skip to content
Browse files

Animation refactor, all works

- Move responsibility back to StateMachine
- Add StateSwitchStop
- Clean up code
- Fix all tests
  • Loading branch information
koenbok committed Sep 16, 2016
1 parent 23a313a commit 5b3372d8af0b4f5d42ac25f465be02d7a691c8ac
@@ -35,26 +35,59 @@ evaluateRelativeProperty = (target, k, v) ->

class exports.Animation extends BaseClass

# 'properties' are the layer properties that will be animated
# 'options' are the animationOptions for this animation
# It's also possible to provide options through an 'options' key in the 'properties object'
# or provide the options at top level and use 'properties' to specify the properties (old API)
constructor: (properties={}, options={}) ->
if properties.options?
_.defaults(options, properties.options)
delete properties.options
else if
#Support old properties: syntax
layer = properties.layer
props =
delete properties.layer
_.defaults(options, properties)
properties = props
properties.layer = layer
@options = _.cloneDeep Defaults.getDefaults "Animation", options
super properties
@layer = properties.layer ? null
constructor: (args...) ->

# Old API detection

# animationA = new Animation
# layer: layerA
# properties:
# x: 100

# print args

layer = null
properties = {}
options = {}

if arguments.length is 3
layer = args[0]
properties = args[1]
options = args[2]

if arguments.length is 2
layer = args[0]
if args[1].properties
properties = args[1].properties
properties = args[1]
options = args[1].options if args[1].options

if arguments.length is 1
layer = args[0].layer
properties = args[0].properties
if args[0].options
options = args[0].options
options = args[0]

delete options.layer
delete options.options

# print "Animation", layer, properties, options

@options = _.cloneDeep(Defaults.getDefaults("Animation", options))


@_layer = layer

unless layer instanceof Layer
throw Error("Animation: missing layer")

@properties = Animation.filterAnimatableProperties(properties)

if properties.origin
@@ -64,6 +97,9 @@ class exports.Animation extends BaseClass
@_originalState = @_currentState()
@_repeatCounter = @options.repeat

@define "layer",
get: -> @_layer

@define "isAnimating",
get: -> @ in @layer.context.animations

@@ -75,8 +111,6 @@ class exports.Animation extends BaseClass

start: =>
if @layer is null
console.error "Animation: missing layer"

@_animator = @_createAnimator()
@_target = @layer
@@ -98,11 +132,11 @@ class exports.Animation extends BaseClass

if _.keys(@_stateA).length is 0
console.warn "Animation: nothing to animate, no animatable properties"
return false
return @_noop()

if _.isEqual(@_stateA, @_stateB)
console.warn "Animation: nothing to animate, all properties are equal to what it is now"
return false
return @_noop()

# If this animation wants to animate a property that is already being animated, it stops
# that currently running animation. If not, it allows them both to continue.
@@ -138,22 +172,28 @@ class exports.Animation extends BaseClass
# If animate is false we set everything immediately and skip the actual animation
start = @_start
start = @_instant if @options.animate is false

# The option keywords animate and instant trigger an instant animation
if @options.animate is false or @options.instant is true
start = @_instant

# If we have a delay, we wait a bit for it to start
if @options.delay
@_delayTimer = Utils.delay(@options.delay, start)

return true

stop: (emit=true)->
stop: (emit=true) ->

if @_delayTimer?
@_delayTimer = null


@emit("stop") if emit
@emit(Events.AnimationStop) if emit"update", @_update)

reverse: ->
@@ -192,15 +232,21 @@ class exports.Animation extends BaseClass

_instant: =>

_noop: =>
return false

_start: =>
Framer.Loop.on("update", @_update)

# Figure out what kind of values we have so we don't have to do it in
@@ -212,8 +258,8 @@ class exports.Animation extends BaseClass
if @_animator.finished()

@@ -38,13 +38,18 @@ Events.Click = Events.TouchEnd
Events.AnimationStart = "start"
Events.AnimationStop = "stop"
Events.AnimationEnd = "end"
Events.AnimationDidStart = "start"
Events.AnimationDidStop = "stop"
Events.AnimationDidEnd = "end"

Events.AnimationDidStart = Events.AnimationStart # Deprecated
Events.AnimationDidStop = Events.AnimationStop # Deprecated
Events.AnimationDidEnd = Events.AnimationEnd # Deprecated

# State events
Events.StateWillSwitch = "willSwitch"
Events.StateDidSwitch = "didSwitch"
Events.StateSwitchStart = "stateswitchstart"
Events.StateSwitchStop = "stateswitchstop"
Events.StateSwitchEnd = "stateswitchend"

Events.StateWillSwitch = Events.StateSwitchStart # Deprecated
Events.StateDidSwitch = Events.StateSwitchEnd # Deprecated

# Scroll events
Events.Scroll = "scroll"
@@ -888,76 +888,45 @@ class exports.Layer extends BaseClass
# Used to animate to a state with a specific name
# We lookup the stateName and call 'animate' with the properties of the state
animateToState: (stateName, options={}) ->
properties = @_stateMachine.switchTo stateName
if @_stateMachine.previousName is @_stateMachine.currentName
shouldChange = false
for property, value of properties
if @[property] isnt value
shouldChange = true
if not shouldChange
return null
finished = options.completion
options.completion = =>
@_stateMachine.emit(Events.StateDidSwitch, @_stateMachine.previousName, @_stateMachine.currentName, @)
@animate properties, options
return @_stateMachine.switchTo(stateName, options)

animate: (properties, options={}) ->
if typeof properties == "string"
stateName = properties
return @animateToState stateName, options

#Support the old properties syntax
# console.warn "Using Layer.animate with 'properties' key is deprecated: please provide properties directly and use the 'options' key to provide animation options instead"
# print "layer.animate", properties, options

# If the properties are a string, we assume it's a state name
if _.isString(properties)
return @animateToState(properties, options)

# Support the old properties syntax, we add all properties top level and
# move the options into an options property.
if properties.hasOwnProperty("properties")
options = properties
properties =

_.defaults(options, properties.options, @animationOptions)
delete properties.options

animatableProperties = Animation.filterAnimatableProperties(properties)
nonAnimatableProperties = _.omit(_.clone(properties), _.keys(animatableProperties))

start = options.start
start ?= true
delete options.start
instant = options.instant ? false
if instant
options.animate = false
delete options.instant
parameters = animatableProperties
parameters.layer = @

animation = new Animation parameters, options
animationFinished = =>
for k, v of nonAnimatableProperties
@[k] = v

animation.once Events.AnimationStop, animationFinished
started = animation.start() if start
if not started
# If the animation didn't start (e.g. because there are no properties to animate), call the finished handler ourselves
# With the new api we treat the properties as animatable properties, and use
# the special options keyword for animation options.
if properties.hasOwnProperty("options")
options = _.defaults(properties.options, options)
delete properties.options

# Merge the animation options with the default animation options for this layer
options = _.defaults(options, @animationOptions)
options.start ?= true

animation = new Animation(@, properties, options)
animation.start() if options.start

return animation

switchInstant: (properties, options={}) ->
options = _.defaults({instant:true}, options)
@animate properties, options

animateToNextState: (stateNames=[], options) ->
if not Array.isArray(stateNames)
if not options? and typeof stateNames is "object"
options = stateNames
stateNames = []
stateNames = Utils.arrayFromArguments arguments
options = {}
nextState =
@animate nextState, options
@animate(properties, _.merge(options, {instant: true}))

animateToNextState: (args..., options={}) ->
states = []
states = _.flatten(args) if args.length
@animate(, options)

animations: ->
# Current running animations on this layer
@@ -1196,8 +1165,12 @@ class exports.Layer extends BaseClass
onDragAnimationEnd: (cb) -> @on(Events.DragAnimationEnd, cb)
onDirectionLockStart: (cb) -> @on(Events.DirectionLockStart, cb)

onStateDidSwitch: (cb) -> @on(Events.StateDidSwitch, cb)
onStateWillSwitch: (cb) -> @on(Events.StateWillSwitch, cb)
onStateSwitchStart: (cb) -> @on(Events.StateSwitchStart, cb)
onStateSwitchStop: (cb) -> @on(Events.StateSwitchStop, cb)
onStateSwitchEnd: (cb) -> @on(Events.StateSwitchEnd, cb)

onStateWillSwitch: (cb) -> @on(Events.StateSwitchStart, cb) # Deprecated
onStateDidSwitch: (cb) -> @on(Events.StateSwitchEnd, cb) # Deprecated

# Gestures

0 comments on commit 5b3372d

Please sign in to comment.
You can’t perform that action at this time.