PureScript Halogen:如何创建每帧触发事件及动画订阅机制?
Absolutely! Halogen has built-in support for exactly this kind of frame-driven animation scenario, and FRP.Event.Time is the right tool for the job. Let’s walk through how to set this up properly, including how to trigger state updates (what you’re referring to as "triggering queries") from your subscription.
Core Idea
To create smooth, frame-aligned animations, you’ll want to hook into the browser’s requestAnimationFrame API—luckily, Event.Time.frame gives you a ready-made event stream that does exactly this. Each event in this stream carries the current timestamp, which you can use to calculate animation progress and update your component’s state.
Step-by-Step Implementation
1. Define Your Component’s Message and State
First, add a message to handle frame ticks, and extend your state to track animation-related data:
import DOM.Time (Ms) import Halogen as H -- Add a frame tick message to your component's Msg type data Msg = FrameTick Ms | StartAnimation | StopAnimation -- plus your existing messages -- Extend state to track animation state data State = State { animationProgress :: Number , startTime :: Maybe Ms , subscriptionId :: Maybe H.SubscriptionId }
2. Set Up the Frame Subscription
You can either subscribe to frames when the component mounts, or dynamically subscribe/unsubscribe when the animation starts/stops. Dynamic subscription is usually more flexible:
import FRP.Event as Event import DOM.Time (now) eval :: Msg -> H.ComponentHTML State Msg o m -> H.ComponentDSL State Msg o m Unit eval msg _ = case msg of StartAnimation -> do -- Capture the start time when the animation begins currentTime <- lift now -- Subscribe to the frame event stream, mapping each tick to your FrameTick message subId <- H.subscribe $ Event.Time.frame |> Event.map FrameTick -- Update state to track the subscription and start time H.modify_ \s -> s { startTime = Just currentTime, subscriptionId = Just subId, animationProgress = 0.0 } StopAnimation -> do -- Clean up the subscription when the animation ends State { subscriptionId } <- H.get traverse_ H.unsubscribe subscriptionId H.modify_ \s -> s { subscriptionId = Nothing, startTime = Nothing } -- Handle the frame tick to update your animation state FrameTick currentTime -> do State { startTime, animationProgress } <- H.get case startTime of Just start -> do -- Calculate progress (example: 1-second animation) let elapsed = currentTime - start newProgress = min 1.0 (elapsed / 1000.0) -- Update the component state with the new progress H.modify_ \s -> s { animationProgress = newProgress } -- Automatically stop the animation when it completes when (newProgress >= 1.0) do H.eval StopAnimation
3. Render Based on Animation State
In your component’s render function, use the animationProgress state value to draw the current frame of your animation:
render :: State -> H.ComponentHTML State Msg o m render (State { animationProgress }) = H.div [ H.style [ ("width", show (animationProgress * 100) <> "%") , ("height", "20px") , ("background-color", "blue") ] ] []
Key Notes
Event.Time.framevs.Event.Time.interval: Useframefor smooth, browser-aligned animations—it syncs with the browser’s repaint cycle, avoiding jank. Useintervalonly if you need fixed-time updates (e.g., a timer that doesn’t depend on repaints).- Subscription Lifecycle: Always clean up subscriptions when they’re no longer needed (like when the animation finishes) to avoid memory leaks. Using
H.subscribereturns aSubscriptionIdthat you can use withH.unsubscribelater. - State Updates: When you call
H.modify_orH.setin theevalfunction, Halogen automatically triggers a re-render with the new state—this is how you "trigger the query" to update your animation frame.
内容的提问来源于stack exchange,提问作者Oleg Shevchenko

