Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
language: node_js
sudo: false
node_js:
- 0.10
dist: trusty
sudo: required
node_js: 5
install:
- npm install -g purescript@0.7.2 pulp
- pulp dep update
- npm install -g purescript pulp bower
- bower install
script:
- pulp build
- pulp test
13 changes: 11 additions & 2 deletions bower.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"version": "1.0.0",
"homepage": "https://github.com/CapillarySoftware/purescript-requestAnimationFrame",
"authors": [
"Isaac Shapira <fresheyeball@gmail.com>"
"Isaac Shapira <fresheyeball@gmail.com>",
"Ryan Rempel <rgrempel@gmail.com>"
],
"description": "Request Animation Frame bindings and polyfill",
"repository": {
Expand All @@ -22,6 +23,14 @@
"package.json"
],
"dependencies": {
"purescript-dom": "~0.2.0"
"purescript-dom": "^1.0.0",
"purescript-eff": "^1.0.0",
"purescript-datetime": "^1.0.0",
"purescript-prelude": "^1.0.0"
},
"devDependencies": {
"purescript-test-unit": "^7.0.0",
"purescript-refs": "^1.0.0",
"purescript-now": "^1.0.0"
}
}
43 changes: 39 additions & 4 deletions docs/DOM/RequestAnimationFrame.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,55 @@

This module exposes a polyfilled `requestAnimationFrame` function.

#### `Request`

``` purescript
newtype Request
```

A request for a callback via `requestAnimationFrame`. You can supply this to
`cancelAnimationFrame` in order to cancel the request.

#### `requestAnimationFrame_`

``` purescript
requestAnimationFrame_ :: forall a eff. Window -> Eff (dom :: DOM | eff) a -> Eff (dom :: DOM | eff) Unit
requestAnimationFrame_ :: forall a eff. Window -> Eff (dom :: DOM | eff) a -> Eff (dom :: DOM | eff) Request
```

Request the specified action be called on the next animation frame, specifying the `Window` object.
Request that the specified action be called on the next animation frame, specifying
the `Window` object.

#### `requestAnimationFrame`

``` purescript
requestAnimationFrame :: forall a eff. Eff (dom :: DOM | eff) a -> Eff (dom :: DOM | eff) Unit
requestAnimationFrame :: forall a eff. Eff (dom :: DOM | eff) a -> Eff (dom :: DOM | eff) Request
```

Request that the specified action be called on the next animation frame.

#### `requestAnimationFrameWithTime`

``` purescript
requestAnimationFrameWithTime :: forall a eff. (Milliseconds -> Eff (dom :: DOM | eff) a) -> Eff (dom :: DOM | eff) Request
```

When it is time for the next animation frame, callback with the then-current
time, and immediately execute the resulting effect.

#### `requestAnimationFrameWithTime_`

``` purescript
requestAnimationFrameWithTime_ :: forall a eff. Window -> (Milliseconds -> Eff (dom :: DOM | eff) a) -> Eff (dom :: DOM | eff) Request
```

Like `requestAnimationFrameWithTime`, but you supply the `Window` object.

#### `cancelAnimationFrame`

``` purescript
cancelAnimationFrame :: forall eff. Request -> Eff (dom :: DOM | eff) Unit
```

Request the specified action be called on the next animation frame.
Cancel a request.


108 changes: 88 additions & 20 deletions src/DOM/RequestAnimationFrame.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,94 @@
"use strict";

// module DOM.RequestAnimationFrame

var requestAnimationFrame = null;

// http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
exports.requestAnimationFrame_ = function(window_) {
return function(action) {

if (!requestAnimationFrame) {
requestAnimationFrame = (function() {
return window_.requestAnimationFrame ||
window_.webkitRequestAnimationFrame ||
window_.mozRequestAnimationFrame ||
function(callback) {
window_.setTimeout(callback, 1000 / 60);
};
})();
// module DOM.RequestAnimationFrame

// The polyfill is a first-class effectful export. We have to store the result
// of the polyfill somewhere, so why not on the provided window? And, then,
// it is just an effect (in Purescript terms), so we can model it that way.
//
// For testing purposes, I've made it so that this actually can run under
// Node -- you just have to unsafely coerce something to be a `Window`.
// Of course, there's no point in doing that in practice, since there's
// no particular resaon to be tied to 60 FPS on Node. (That is, you'd want
// a completely different API to set your own frame rate on Node).
//
// The polyfill is based on the following gist:
//
// https://gist.github.com/jonasfj/4438815
//
// which, in turn, gives the following credits:
//
// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
//
// requestAnimationFrame polyfill by Erik Möller
// fixes from Paul Irish and Tino Zijdel
// list-based fallback implementation by Jonas Finnemann Jensen
exports.applyPolyfillIfNeeded = function (window_) {
return function () {
// We check for both, since we need both and they need to be consistent
if (!window_.requestAnimationFrame || !window_.cancelAnimationFrame) {
var vendors = ['webkit', 'moz'];

for (var x = 0; x < vendors.length && !window_.requestAnimationFrame; ++x) {
window_.requestAnimationFrame = window_[vendors[x] + 'RequestAnimationFrame'];

window_.cancelAnimationFrame =
window_[vendors[x] + 'CancelAnimationFrame'] ||
window_[vendors[x] + 'CancelRequestAnimationFrame'];
}

// Again, we double-check for both
if (!window_.requestAnimationFrame || !window_.cancelAnimationFrame) {
// If still not present, apply the polyfill.
var tid = null, cbs = [], nb = 0, ts = Date.now();

var animate = function animate () {
var i, clist = cbs, len = cbs.length;
tid = null;
ts = Date.now();
cbs = [];
nb += clist.length;

for (i = 0; i < len; i++) {
if (clist[i]) clist[i](ts);
}
};

window_.requestAnimationFrame = function (cb) {
if (tid === null) {
tid = setTimeout(animate, Math.max(0, 20 + ts - Date.now()));
}

return cbs.push(cb) + nb;
};

window_.cancelAnimationFrame = function (id) {
delete cbs[id - nb - 1];
};
}
}
};
};

// The rest assume that the polyfill has already been applied, if needed
exports.requestAnimationFrameImpl = function (window_) {
return function (callback) {
return function () {
return window_.requestAnimationFrame(function (time) {
// The callback is a function from the time to an effect. So
// we call the callback to get the effect, and then we immediately
// execute it (since now is the time we promised to do that).
callback(time)();
});
};
};
};

return function() {
return requestAnimationFrame(action);
exports.cancelAnimationFrameImpl = function (window_) {
return function (requestID) {
return function () {
window_.cancelAnimationFrame(requestID);
};
}
};
};
73 changes: 58 additions & 15 deletions src/DOM/RequestAnimationFrame.purs
Original file line number Diff line number Diff line change
@@ -1,22 +1,65 @@
-- | This module exposes a polyfilled `requestAnimationFrame` function.
module DOM.RequestAnimationFrame
( requestAnimationFrame
, requestAnimationFrame_
) where
( Request
, requestAnimationFrame, requestAnimationFrame_
, requestAnimationFrameWithTime, requestAnimationFrameWithTime_
, cancelAnimationFrame
) where

import Prelude
import DOM (DOM)
import DOM.HTML (window)
import DOM.HTML.Types (Window)

import Control.Monad.Eff
import Data.Time.Duration (Milliseconds(..))
import Control.Monad.Eff (Eff)

import DOM (DOM())
import DOM.HTML (window)
import DOM.HTML.Types (Window())
import Prelude (Unit, bind, (>>=), pure, flip, ($), const, (<<<))


-- | A request for a callback via `requestAnimationFrame`. You can supply this to
-- | `cancelAnimationFrame` in order to cancel the request.
newtype Request = Request
{ win :: Window
, id :: RequestID
}

foreign import data RequestID :: *

foreign import applyPolyfillIfNeeded :: forall eff. Window -> Eff (dom :: DOM | eff) Unit

foreign import requestAnimationFrameImpl :: forall a eff. Window -> (Number -> Eff (dom :: DOM | eff) a) -> Eff (dom :: DOM | eff) RequestID

foreign import cancelAnimationFrameImpl :: forall eff. Window -> RequestID -> Eff (dom :: DOM | eff) Unit


-- | Request that the specified action be called on the next animation frame, specifying
-- | the `Window` object.
requestAnimationFrame_ :: forall a eff. Window -> Eff (dom :: DOM | eff) a -> Eff (dom :: DOM | eff) Request
requestAnimationFrame_ win =
requestAnimationFrameWithTime_ win <<< const


-- | Request that the specified action be called on the next animation frame.
requestAnimationFrame :: forall a eff. Eff (dom :: DOM | eff) a -> Eff (dom :: DOM | eff) Request
requestAnimationFrame action =
window >>= (flip requestAnimationFrameWithTime_) (const action)


-- | When it is time for the next animation frame, callback with the then-current
-- | time, and immediately execute the resulting effect.
requestAnimationFrameWithTime :: forall a eff. (Milliseconds -> Eff (dom :: DOM | eff) a) -> Eff (dom :: DOM | eff) Request
requestAnimationFrameWithTime func =
window >>= (flip requestAnimationFrameWithTime_) func


-- | Like `requestAnimationFrameWithTime`, but you supply the `Window` object.
requestAnimationFrameWithTime_ :: forall a eff. Window -> (Milliseconds -> Eff (dom :: DOM | eff) a) -> Eff (dom :: DOM | eff) Request
requestAnimationFrameWithTime_ win func = do
applyPolyfillIfNeeded win
id <- requestAnimationFrameImpl win (func <<< Milliseconds)
pure $ Request {id, win}

-- | Request the specified action be called on the next animation frame, specifying the `Window` object.
foreign import requestAnimationFrame_ :: forall a eff. Window -> Eff (dom :: DOM | eff) a -> Eff (dom :: DOM | eff) Unit

-- | Request the specified action be called on the next animation frame.
requestAnimationFrame :: forall a eff. Eff (dom :: DOM | eff) a -> Eff (dom :: DOM | eff) Unit
requestAnimationFrame action = do
w <- window
requestAnimationFrame_ w action
-- | Cancel a request.
cancelAnimationFrame :: forall eff. Request -> Eff (dom :: DOM | eff) Unit
cancelAnimationFrame (Request {win, id}) = cancelAnimationFrameImpl win id
9 changes: 9 additions & 0 deletions test/Main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// We're just returning an object that doesn't do requestAnimationFrame,
// so that the polyfill will be applied. We do it effectfully so that
// we can control when a fresh polyfill happens (otherwise, a previous
// test affects when the next animation frame is due).
exports.fakeWindow = function () {
return function () {
return {};
};
};
Loading