abe33/awesome_form

View on GitHub
app/assets/javascripts/awesome_ui/animations.js.coffee

Summary

Maintainability
Test Coverage
window.requestAnimationFrame =
  window.requestAnimationFrame or
  window.webkitRequestAnimationFrame or
  window.mozRequestAnimationFrame or
  (callback) -> window.setTimeout(callback, 1000 / 60)

widgets.key_spline = (m_x1, m_y1, m_x2, m_y2) ->
  # linear
  A = (a1, a2) ->
    1.0 - 3.0 * a2 + 3.0 * a1
  B = (a1, a2) ->
    3.0 * a2 - 6.0 * a1
  C = (a1) ->
    3.0 * a1

  # Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
  calc_bezier = (a_t, a1, a2) ->
    ((A(a1, a2) * a_t + B(a1, a2)) * a_t + C(a1)) * a_t

  # Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
  get_slope = (a_t, a1, a2) ->
    3.0 * A(a1, a2) * a_t * a_t + 2.0 * B(a1, a2) * a_t + C(a1)

  get_t_for_x = (a_x) ->
    # Newton raphson iteration
    guess_t = a_x
    i = 0

    while i < 4
      current_slope = get_slope(guess_t, m_x1, m_x2)
      return guess_t  if current_slope is 0.0
      currentX = calc_bezier(guess_t, m_x1, m_x2) - a_x
      guess_t -= currentX / current_slope
      ++i
    guess_t

  return (a_x) ->
    return a_x if m_x1 is m_y1 and m_x2 is m_y2
    calc_bezier get_t_for_x(a_x), m_y1, m_y2

widgets.Easing =
  linear: (t) -> t
  ease: widgets.key_spline(0.25, 0.1, 0.25, 1.0)
  linear: widgets.key_spline(0.00, 0.0, 1.00, 1.0)
  ease_in: widgets.key_spline(0.42, 0.0, 1.00, 1.0)
  ease_out: widgets.key_spline(0.00, 0.0, 0.58, 1.0)
  ease_in_out: widgets.key_spline(0.42, 0.0, 0.58, 1.0)

class widgets.PropertyTransition
  constructor: (@from=0, @to) ->

  get: (t) -> "#{@from + (@to - @from) * t}px"

class widgets.Animation
  constructor: (@element, @properties, @duration, @easing) ->
    @time = 0
    @finished = false

    @property_transitions = {}
    for k,v of @properties
      from = null
      to = null

      if typeof v is 'object'
        {from, to} = v
      else
        to = v

      unless from?
        from = @element.style[k]

      if from is ''
        from = null

      @property_transitions[k] = new widgets.PropertyTransition from, to

  animate: (t) ->
    @time += t
    easing_position = @easing @time / @duration

    if @time >= @duration
      @finished = true
      easing_position = 1

    for k,v of @properties
      @element.style[k] = @property_transitions[k].get(easing_position)

class widgets.AnimationManager
  constructor: ->
    @animations = []
    @last_time = new Date()
    @animate()

  animate: ->
    time = new Date()
    dif = time - @last_time

    @animations = @animations.filter (animation) =>
      animation.animate(dif)
      not animation.finished

    @last_time = time
    requestAnimationFrame => @animate()

  add: (animation) ->
    @animations.push animation
    animation.animate(0)

widgets.animate = (element, options) ->
  widgets.animation_manager ||= new widgets.AnimationManager

  easing = widgets.Easing.linear
  duration = 1000

  if options.easing?
    easing = options.easing
    delete options.easing

  if options.during?
    duration = options.during
    delete options.during

  animation = new widgets.Animation element, options, duration, easing

  widgets.animation_manager.add animation