Skip to content

Commit

Permalink
initial keyframe work
Browse files Browse the repository at this point in the history
  • Loading branch information
Koen Bok committed Mar 10, 2013
1 parent 7d65cab commit 589a47e
Show file tree
Hide file tree
Showing 11 changed files with 1,972 additions and 1,007 deletions.
1,090 changes: 759 additions & 331 deletions build/framer.js

Large diffs are not rendered by default.

267 changes: 161 additions & 106 deletions src/animation.coffee
@@ -1,15 +1,17 @@
{Spring} = require "./primitives/spring"
{EventEmitter} = require "./eventemitter"
utils = require "../utils"
css = require "../css"

require "./utils"
{EventEmitter} = require "./eventemitter"
{Matrix} = require "../primitives/matrix"

PROPERTIES = ["view", "curve", "time", "origin", "tolerance"]
spring = require "../curves/spring"
bezier = require "../curves/bezier"

parseCurve = (a) ->
parseCurve = (a, prefix) ->

# "spring(1, 2, 3)" -> 1, 2, 3

a = a.replace "spring", ""
a = a.replace prefix, ""
a = a.replace /\s+/g, ""
a = a.replace "(", ""
a = a.replace ")", ""
Expand All @@ -18,132 +20,185 @@ parseCurve = (a) ->
return a.map (i) -> parseFloat i


class exports.Animation extends EventEmitter
class Animation extends EventEmitter

AnimationProperties: ["view", "curve", "time", "origin", "tolerance", "precision"]
AnimatableProperties: ["x", "y", "z", "scale", "scaleX", "scaleY", "scaleZ", "rotate", "rotateX", "rotateY", "rotateZ"] # z, width, height, opacity

TransformPropertyMap:
x: {name: "translateX", unit: "px"}
y: {name: "translateY", unit: "px"}
z: {name: "translateZ", unit: "px"}
rotateX: {name: "rotateX", unit: "deg"}
rotateY: {name: "rotateY", unit: "deg"}
rotateZ: {name: "rotateZ", unit: "deg"}
scaleX: {name: "scaleX", unit: ""}
scaleY: {name: "scaleY", unit: ""}
scaleZ: {name: "scaleZ", unit: ""}
# scale: {name: "scale", unit: ""}

constructor: (args) ->
super

for p in PROPERTIES
# Set all properties
for p in @AnimationProperties
@[p] = args[p]

@modifiers = args.modifiers or {}
@endProperties = args.properties
@originalProperties = @view.properties
# Set all the defaults

@time ?= 1000
@curve ?= "linear"
@precision ?= 30


@curveValues = @_parseCurve @curve

@animationName = "framer-animation-#{utils.uuid()[..8]}"


# Clean up the animation wishes

# TODO: test if we are trying to animate something that cannot animate

propertiesA = @view.properties
propertiesB = args.properties

@propertiesA = {}
@propertiesB = {}

for k in @AnimatableProperties

@propertiesA[k] = propertiesA[k]

if propertiesB.hasOwnProperty k
@propertiesB[k] = propertiesB[k]
else
@propertiesB[k] = propertiesA[k]

#console.log "#{k} #{@propertiesA[k]} -> #{@propertiesB[k]}"

@keyFrameAnimationCSS = @_css()

start: (callback) =>

@beginProperties = @originalProperties
@view._animationTransformOrigin = @origin
setTimeout =>
@_start callback
, 0
# @_start callback

stop: ->
@_stop = true
@_end()
@view.style.webkitTransform = @view.computedStyle.webkitTransform
# console.log "Animation.stop", @

_end: (callback) =>
# @view._animated = false
@view._animationDuration = 0
@emit "end", @
utils.remove @view._animations, @
callback?()

_start: (callback) =>
# console.log "Animation.start #{@animationName}"

@emit "start", @
@view._animations.push @
# console.log @keyFrameAnimationCSS

@_stop = false

time = @time or 300
curve = @curve or "linear"
# TODO: see if other animations are running and cancel them

# linear, ease, ease-in, ease-out, ease-in-out
# cubic-bezier(0.76, 0.18, 0.25, 0.75)
# spring(tension, friction, velocity)

if curve[..5] == "spring"

if @time
console.log "view.animate: ignoring time for spring"

values = parseCurve curve
options =
tension: values[0]
friction: values[1]
velocity: values[2]
speed: 1/60
tolerance: @tolerance or 0.01

@_startSpring options, callback

css.addStyle "
#{@keyFrameAnimationCSS}
.#{@animationName} {
-webkit-animation-duration: #{@time/1000}s;
-webkit-animation-name: #{@animationName};
-webkit-animation-timing-function: linear;
-webkit-animation-fill-mode: both;
return

@_animate @endProperties, curve, time, =>
@_end callback
}"

@view.class += " #{@animationName}"

finalize = =>
# console.log "Animation.end #{@animationName}"

@view._element.removeEventListener "webkitAnimationEnd", finalize

@view.removeClass @animationName
#
for k, v of @propertiesB
# console.log "#{k} #{@propertiesB[k]}"
@view[k] = @propertiesB[k]

# @view._matrix.logValues()




# console.log "@propertiesB", @propertiesB


# @view.properties = @propertiesB

_startSpring: (options, callback) =>


@emit "end"
callback?()

@view._element.addEventListener "webkitAnimationEnd", finalize


@spring = new Spring options


beginState = {}
stop: =>
@view.style["-webkit-animation-play-state"] = "paused"
@view._matrix = new WebKitCSSMatrix @view.computedStyle["-webkit-transform"]
@view.removeClass @keyFrameAnimation.name

_css: ->

animationName = @animationName

stepIncrement = 0
stepDelta = 100 / (@curveValues.length - 1)

cssString = []
cssString.push "@-webkit-keyframes #{animationName} {\n"

deltas = {}

for propertyName, value of @propertiesA
deltas[propertyName] = (@propertiesB[propertyName] - @propertiesA[propertyName]) / 100.0

@curveValues.map (springValue) =>

position = stepIncrement * stepDelta

for k, v of @endProperties
deltas[k] = (@endProperties[k] - @beginProperties[k]) / 100.0
beginState[k] = @beginProperties[k]

run = =>
cssString.push "\t#{position.toFixed(2)}%\t{ -webkit-transform: "

# If the spring stopped moving we can stop
if not @spring.moving or @_stop
return @_end callback
m = new Matrix()

value = @spring.next()
for propertyName, value of @propertiesA
m[propertyName] = springValue * deltas[propertyName] + @propertiesA[propertyName]

cssString.push m.matrix().cssValues() + "; }\n"

nextState = {}
stepIncrement++

for k, v of beginState

nextState[k] = (deltas[k] * value) + beginState[k]

if @modifiers[k]
nextState[k] = @modifiers[k](nextState[k])

@_animate nextState, "linear", @spring.speed, run

run()

_animate: (properties, curve, time, callback) =>

# @view._animated = true
@view._animationDuration = time
@view._animationTimingFunction = curve
cssString.push "}\n"

# FIX: we should probarbly do it like this:
# http://stackoverflow.com/questions/2087510/callback-on-css-transition
return cssString.join ""

@timer = setTimeout =>
# @view._animated = false
# @view._animationDuration = 0
callback?()
, time

for k, v of properties
if k in ["rotateX", "rotateY", "rotateZ", "opacity", "scale", "x", "y", "z", "width", "height"]
@view[k] = properties[k]
_parseCurve: (curve) ->

reverse: ->
options =
view: @view
properties: @originalProperties
curve ?= ""
curve = curve.toLowerCase()

for p in PROPERTIES
options[p] = @[p]

return new Animation options
if curve in ["linear"]
return bezier.defaults.Linear @precision, @time
else if curve in ["ease"]
return bezier.defaults.Ease @precision, @time
else if curve in ["ease-in"]
return bezier.defaults.EaseIn @precision, @time
else if curve in ["ease-out"]
return bezier.defaults.EaseOut @precision, @time
else if curve in ["ease-in-out"]
return bezier.defaults.EaseInOut @precision, @time
else if curve[.."cubic-bezier".length-1] is "cubic-bezier"
v = parseCurve curve, "cubic-bezier"
return bezier.BezierCurve v[0], v[1], v[2], v[3], @precision, @time
else if curve[.."spring".length-1] is "spring"
v = parseCurve curve, "spring"
return spring.SpringCurve v[0], v[1], v[2], @precision
else

# The default curve is linear
console.log "Animation.parseCurve: could not parse curve '#{curve}'"
return bezier.defaults.Linear @precision, @time


exports.Animation = Animation
43 changes: 32 additions & 11 deletions src/css.coffee
@@ -1,18 +1,39 @@
addStyle = (css) ->
STYLESHEET_ID = "FramerCSS"

_STYLESHEET = null

exports.addStyle = (css) ->

# styleSheet = document.getElementById STYLESHEET_ID
#
# if not styleSheet
#
# head = document.getElementsByTagName "head"
# head = head[0] if head
#
# if not head
# head = document.body or document.documentElement
#
# styleSheet = document.createElement "style"
# styleSheet.id = STYLESHEET_ID
#
# head.appendChild styleSheet
#
# styleSheet.appendChild document.createTextNode css

head = document.getElementsByTagName "head"
head = head[0] if head
# if _STYLESHEET is null
_STYLESHEET = document.createElement('style');
document.head.appendChild(_STYLESHEET);

_STYLESHEET.innerHTML += css

# style = document.createElement('style');
# style.innerHTML = css


if not head
head = document.body or document.documentElement

baseStyle = document.createElement "style"
baseStyle.id = "UILayer-base-style"
baseStyle.appendChild document.createTextNode(css)

head.appendChild baseStyle

addStyle "
exports.addStyle "
.uilayer {
display: block;
visibility: visible;
Expand Down
6 changes: 2 additions & 4 deletions src/init.coffee
@@ -1,5 +1,4 @@
require "./css"

css = require "./css"
utils = require "./utils"
debug = require "./debug"

Expand All @@ -12,7 +11,6 @@ debug = require "./debug"
{Animation} = require "./animation"

{Frame} = require "./primitives/frame"
{Spring} = require "./primitives/spring"

Global = {}
Global.View = View
Expand All @@ -22,11 +20,11 @@ Global.ImageView = ImageView
# Global.TextView = TextView
Global.Animation = Animation
Global.Frame = Frame
Global.Spring = Spring

Global.utils = utils
Global.ViewList = ViewList
Global.debug = debug.debug
Global.css = css

if window
window.Framer = Global
Expand Down

0 comments on commit 589a47e

Please sign in to comment.