diff --git a/jsaddle-dom.cabal b/jsaddle-dom.cabal
--- a/jsaddle-dom.cabal
+++ b/jsaddle-dom.cabal
@@ -1,5 +1,5 @@
 name: jsaddle-dom
-version: 0.9.0.0
+version: 0.9.1.0
 cabal-version: >=1.22
 build-type: Simple
 license: MIT
@@ -1213,7 +1213,7 @@
         base-compat >=0.9.0 && <0.10,
         transformers >=0.2 && <0.6,
         text >=0.11.0.6 && <1.3,
-        jsaddle >=0.9.0.0 && <0.10,
+        jsaddle >=0.9.3.0 && <0.10,
         lens >=4.12.3 && <4.16
     default-language: Haskell2010
     hs-source-dirs: src
diff --git a/src/JSDOM.hs b/src/JSDOM.hs
--- a/src/JSDOM.hs
+++ b/src/JSDOM.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE OverloadedStrings, PatternSynonyms #-}
+{-# LANGUAGE OverloadedStrings, PatternSynonyms, RecursiveDo #-}
 module JSDOM (
   currentWindow
 , currentWindowUnchecked
@@ -8,16 +8,29 @@
 , syncAfter
 , waitForAnimationFrame
 , nextAnimationFrame
+, AnimationFrameHandle
+, inAnimationFrame
+, inAnimationFrame'
 , catch
 , bracket
 ) where
 
-import JSDOM.Types
-       (FromJSVal(..), MonadDOM, liftDOM, Document(..), Window(..))
+import Control.Monad (void, forM_, when)
+import Control.Monad.IO.Class (MonadIO(..))
+import Control.Concurrent.MVar (putMVar, takeMVar)
+import GHCJS.Concurrent (OnBlocked(..))
 import Language.Javascript.JSaddle
        (syncPoint, syncAfter, waitForAnimationFrame,
         nextAnimationFrame, catch, bracket)
-import Language.Javascript.JSaddle.Object (jsg)
+import Language.Javascript.JSaddle.Types (JSContextRef(..))
+import Language.Javascript.JSaddle.Object (freeFunction, jsg)
+import Language.Javascript.JSaddle.Monad (askJSM)
+import JSDOM.Types
+       (Callback(..), RequestAnimationFrameCallback(..), FromJSVal(..),
+        MonadDOM, liftDOM, Document(..), Window(..), JSM, JSContextRef(..))
+import JSDOM.Generated.RequestAnimationFrameCallback
+       (newRequestAnimationFrameCallbackSync)
+import JSDOM.Generated.Window (requestAnimationFrame)
 
 currentWindow :: MonadDOM m => m (Maybe Window)
 currentWindow = liftDOM $ jsg ("window" :: String) >>= fromJSVal
@@ -31,3 +44,37 @@
 currentDocumentUnchecked :: MonadDOM m => m Document
 currentDocumentUnchecked = liftDOM $ jsg ("document" :: String) >>= fromJSValUnchecked
 
+data AnimationFrameHandle = AnimationFrameHandle
+
+{- |
+     Run the action in an animationframe callback. The action runs in a
+     synchronous thread, and is passed the high-performance clock time
+     stamp for that frame.
+ -}
+inAnimationFrame :: OnBlocked       -- ^ what to do when encountering a blocking call
+                 -> (Double -> JSM ())  -- ^ the action to run
+                 -> JSM AnimationFrameHandle
+inAnimationFrame _ f = do
+    handlersMVar <- animationFrameHandlers <$> askJSM
+    handlers <- liftIO $ takeMVar handlersMVar
+    when (null handlers) $ do
+        win <- currentWindowUnchecked
+        rec cb@(RequestAnimationFrameCallback (Callback f)) <- newRequestAnimationFrameCallbackSync $ \t -> do
+              freeFunction f
+              handlersToRun <- liftIO $ takeMVar handlersMVar
+              liftIO $ putMVar handlersMVar []
+              forM_ (reverse handlersToRun) (\handler -> handler t)
+        void $ requestAnimationFrame win cb
+    liftIO $ putMVar handlersMVar (f : handlers)
+    return AnimationFrameHandle
+
+{- |
+     Run the action in an animationframe callback. The action runs in a
+     synchronous thread, and is passed the high-performance clock time
+     stamp for that frame.  On GHCJS this version will continue
+     asynchronously if it is not possible to complete the callback
+     synchronously.
+ -}
+inAnimationFrame' :: (Double -> JSM ())  -- ^ the action to run
+                 -> JSM AnimationFrameHandle
+inAnimationFrame' = inAnimationFrame ContinueAsync
