WebDesign Dackel

requestAnimationFrameの簡単なラッパークラスをCoffeeScriptで書いてみた

requestAnimationFrameの簡単なラッパークラスをCoffeeScriptで書いてみた

Hatena0
Google+0
Pocket0
Feedly0

はじめに

JavaScriptでアニメーションをする時に定番化してきているrequestAnimationFrameですが、npmで探せば、使いやすそうなライブラリが沢山出てきます。
でも、ちょっとしたコードで使いたいときにわざわざライブラリを入れるのもなぁ〜と思い、少し前にCoffeeScriptで簡単なラッパークラスを書いたので共有したいと思います。

CoffeeScriptで書いたクラス

下記ではAnimationFrameというクラスを定義しています。

class AnimationFrame

  @raf = 
    window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    window.msRequestAnimationFrame ||
    window.oRequestAnimationFrame ||
    (tick) -> 
      setTimeout tick, 13

  @caf = 
    window.cancelAnimationFrame ||
    window.cancelRequestAnimationFrame ||
    window.webkitCancelAnimationFrame ||
    window.webkitCancelRequestAnimationFrame ||
    window.mozCancelAnimationFrame ||
    window.mozCancelRequestAnimationFrame ||
    window.msCancelAnimationFrame ||
    window.msCancelRequestAnimationFrame ||
    window.oCancelAnimationFrame ||
    window.oCancelRequestAnimationFrame ||
    (id) ->
      clearTimeout id

  _frame = null
  _startTime = 0
  _id = null
  _tick = null
  _running = false

  constructor: (tick) ->
    _tick = tick

  run: (delay = 0) ->
    self = @
    setTimeout ->
      if !self.isRunning()
        _startTime = new Date().valueOf()
        _running = true
        _frame.call self
    , delay
    self

  isRunning: ->
    _running == true

  stop: ->
    AnimationFrame.caf.call(window, _id)
    _startTime = 0
    _running = false
    @

  _frame = ->
    self = @
    _id = AnimationFrame.raf.call window, (timestamp) ->
      if _running == true
        timestamp ?= new Date().valueOf() - _startTime
        _tick.call self, timestamp
        _frame.call self
    @

実際コンパイルされたコードを確認したい場合は、CoffeeScriptTry CoffeeScriptに貼り付けるのが簡単かと思います。

使い方

ちっちゃいクラスだしメソッドも少ないのですが一応ご紹介しまっす!

各フレーム毎の処理を用意

AnimationFrameのコンストラクタに繰り返し用の関数を指定します。

animator = new AnimationFrame ->
  # ここで各フレームのアニメーションを定義

アニメーションを開始

返ってきたインスタンスのrun()メソッドを実行すると、コンストラクタで指定した関数が繰り返し実行されます。

animator.run()

また、下記の様にミリ秒を指定することで開始時間を遅延することが出来ます。

animator.run(1000) #1秒後にアニメーション開始

アニメーションを停止

止めるときはstop()メソッドを実行します。

animator.stop()

アニメーション実行中か判定

isRunning()メソッドの戻り値がtrueなら実行中、falseならアニメーションが止まっています。

animator.isRunning()

実際に使ってみる

下記では要素が円運動をするアニメーションとなっています。DEMOではjQueryを使っています。
動作はDEMOから確認出来ます。

DEMO

HTML

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>AnimationFrame</title>
</script>
<style type="text/css">
  body {
    font-family:sans-serif;
    font-size:18px;
    color:#333;
  }
  .container {
    position:relative;
    width:600px;
    margin:0 auto;
  }

  #circle {
    position:absolute;
    width:150px;
    height:150px;
    background:#333;
    border-radius:100%;
  }
</style>
</head>
<body>
  <div class="container">
    <h1>AnimationFrame</h1>
    <div id="circle"></div>
    <button id="stop">stop</button>
    <button id="resume">resume</button>
  </div>
  <script type="text/javascript" src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
  <script type="text/javascript" src="AnimationFrame.js"></script>
</body>
</html>

CoffeeScript

$ ->

  $stop = $("#stop")
  $resume = $("#resume")
  $circle = $("#circle")

  r = 100
  degree = 0
  centerX = 300 - ( $circle.width() / 2 )
  centerY = 180


  # アニメーション定義
  animator = new AnimationFrame (timestamp) ->
    radian = Math.PI / 180 * degree
    degree += 5

    $circle.css
      top: centerY + r * Math.sin radian
      left: centerX +  r * Math.cos radian


  # アニメーション開始
  animator.run() # animator.run(1000) で1秒後にスタート


  # stopボタンのクリックでアニメーションを停止
  $stop.on "click", (e) ->
    animator.stop()


  # resumeボタンで再度アニメーションを開始
  $resume.on "click", (e) ->
    animator.run()

IE7や8などの下位互換について

単純にrequestAnimationFrameが定義されていない場合、setTimeoutでフォールバックしているので同じ様に動作する様になっています。
また、各フレームでrequestAnimationFrameはタイムスタンプを返すので、そこも各フレーム実行時のミリ秒から開始時間を引くことで擬似的に対応しています。
cancelAnimationFrameも同様です。

参考サイト

下記サイトがとっても参考になりました!

おわりに

クラスのコード自体複雑なことはしていないので、あまりこだわらず、さっと使いたい時には丁度いいかもです。