LoginSignup
33

More than 5 years have passed since last update.

Angular.jsでDeferredしたくて頑張ってみたらcss animationが綺麗に書けた

Last updated at Posted at 2013-07-18

Angular.jsでDeferredしたい

jQueryを使わずにAngular.jsだけで実装したいけど、
うまいことDeferredできないかなぁ、と思って。

Serviceに$qっていうのがあった

A promise/deferred implementation inspired by Kris Kowal's Q.

こんな感じで使えるっぽい。

myDeferredA = (state) ->
    deferred = $q.defer()
    successMsg = 'Success!'
    errorMsg = 'Error!'

    setTimeout( ->
        $scope.$apply ->
            if state == 0
                deferred.resolve successMsg
            else
                deferred.reject errorMsg
    , 3000)

    deferred.promise

promiseA = myDeferredA(Math.random()*2|0)
promiseA.then (msg) ->
    console.log "success: #{msg}"
, (msg) ->
    console.log "error: #{msg}"

非同期処理の場合Viewの更新が必要になるので、
$scope.$applyの中でdeferred.resolve/rejectする。

jQuery.Deferredみたいに.done()/.fail()は無いので、
.then(successCallback, errorCallback)で成功/失敗を分ける。

さらに、`$q.when`を使えば並列処理もできる。 `$q.all`を使うと、複数のpromiseを一つにまとめ、 そのどれか一つでも`resolve`/`reject`すれば処理を完了する。 \# 今ひとつ挙動がつかめてなくてちょっと理解度が足りない

$q.whenと$q.allについては再度検証中

CSS3 animationを簡単にチェーンできるやつを作ってみる

Deferred を使おう:CSSトランジション終了の検知を参考にしながら、よしなに実装してみる。

理想はこんな感じ

animation = cssAnimation.create element
animation
    .end(->
        ...
    ).end(->
        ...
    ).end

あらかじめ、$qと$windowをDIしておく

class Animation
    constructor: (elem) ->
        css = $window.getComputedStyle elem[0], ''
        duration = css[supportAnimation.duration] || '0s'
        @elem = elem
        @_msec = +duration * 1000
        @_queue = []
        @_isProgress = false
        @_promise = null

このへんはほぼ参考サイトのまんま。
promiseに.then()を追加していく(ような)形にしたかったので、プロパティとして用意しておく。
# msecとか結局使ってなかった…

# eventをbindしてdeferオブジェクトを返す
set: (event, callback, isReturnPromise) ->
  isReturnPromise = false unless isReturnPromise?
  _dfdFunc = () =>
    dfd = $q.defer()

    @elem.bind event, =>
      @elem.unbind event, callback
      # callbackの中でresolve/rejectできるようにしておく
      callback.call(@, dfd)
      dfd.resolve() unless isReturnPromise

    return dfd.promise

  @done _dfdFunc
  return @

# animationEnd用のsetラッパー
end: (callback, isReturnPromise) ->
  return @set supportAnimation.end, callback, isReturnPromise

setメソッドでdeferredオブジェクトを作りつつ、@elemにeventをバインドしていく。
callbackの中でsetTimeoutとかすることも踏まえて(と言うか実際やりたかった)、callbackにdeferredオブジェクトを渡しておく。
(callbackの引数でdfdを拾ってあげる)
さらに、setの第3引数(endの場合第2引数)でフラグを受け取ってresolve()するかしないかも分けておく。

最後にdoneメソッドにpromiseを渡して、thisを返しておく。

後々animationEndだけじゃなくなることも考えて、
endメソッドはsetメソッドのラッパーにしておく。

supportAnimationbootstrap-transition.jsから拝借
# はじめはdurationも使う予定だった

supportAnimation = do ->
    el = document.createElement 'tada'
    transEndEventNames =-
        'WebkitAnimation' : 'webkitAnimationEnd'
        'animation'          : 'animationend'
    transPropNames =-
        'WebkitAnimation' : 'webkitAnimationDuration'
        'animation'          : 'animationDuration'

    for name of transEndEventNames
        if el.style[name] isnt undefined
            return {
                end         : transEndEventNames[name],
                duration : transPropNames[name]
            }

doneメソッドはanimationEnd以外の非同期処理でチェーンする場合に使えるインターフェースとして。
setの中からも使うようにした。

# promiseのsuccessCallbackに`callback`を追加する
# promiseが無ければ`callback`を実行して、promiseを作る
done: (callback) ->
    if @_promise == null
        @_promise = callback.call(@)
    else
        @_promise = @_promise.then(callback)

@_promise.then()の返り値で上書きすることで.then()のチェーンっぽくなった(…かな?)

最後にcreateメソッドを返しておしまい。

return {
    create: (elem) ->
        new Animation(elem)
}

こんな感じで書けるようになった

module.controller 'hoge', ['cssAnimation', (cssAnimation) ->
    element = angular.element document.querySelector '.animation'
    animation = cssAnimation.create element
    animation
        .end(->
            console.log '1st animation is ended'
        ).end(->
            console.log '2nd animation is ended'
        ).end((dfd) ->
            $timeout(->
                console.log '3rd animation is ended'
                dfd.resolve()
            , 3000)
        , true).done(->
            console.log 'all animations are ended'
        )
]

今のところちゃんと動いてるけど、テスト書いてないので何かあったらどうしよう((((;゚Д゚))))

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33