A pure Elm subscription-based animation engine with full control, looping, and seamless mid-flight interruptions.
The pure-Elm counterpart to the WAAPI engine. Same feature set — looping, retargeting, per-axis freezing, pause/resume,
mid-flight queries, springs — but driven by Elm's update loop instead of the browser compositor. No JavaScript companion,
no ports, and interpolated values are available synchronously on every frame.
Reach for Sub when you want the WAAPI-like feature set but without the JS dependency.
📖 See Sub Engine Documentation and Engine Overview for details.
The name of the animation group you want to target.
Type alias for the base AnimBuilder type.
Builder type for Sub-only builders.
Use this in type annotations when a builder function should only work with this engine.
📖 See Engine Capabilities for patterns and examples.
📖 See Initialize for details.
Initialize the engine state with a list of property initializers.
import Anim.Engine.Sub as Sub
import Anim.Property.Opacity as Opacity
Sub.init
[ Opacity.init "animGroupName" 0.5
, ... -- other property initializers
]Select the animation group that subsequent property builders will target.
Sub.for "animGroupName"
>> Opacity.begin
>> Opacity.to 0.2
>> Opacity.end📖 See Triggering Animations for details.
Trigger animations.
import Anim.Engine.Sub as Sub
{ model
| animState =
Sub.animate model.animState <|
Sub.for "animGroupName"
>> entryAnim
}Change the targeted properties instantly to their new values. If currently animating, stop.
This is a convenience function for immediately snapping properties or axes to new values
without needing to construct a full animation builder with animate.
Just target the properties or axes you want to change, and any properties or axes you don't mention will be left untouched - if mid-flight, they will continue.
import Anim.Engine.Sub as Sub
import Anim.Property.Translate as Translate
{ model
| animState =
Sub.retarget model.animState <|
Sub.for "animGroupName"
>> Translate.begin
>> Translate.toY 0
>> Translate.end
}Update one or more animation groups after a layout change, or resize event.
Use this to set the new pixel bounds for your animation groups, and the animations will adjust proportionally to the new bounds.
Typical resize handler:
import Anim.Engine.Sub as Sub
import Anim.Property.Translate as Translate
GotTrack (Ok { element }) ->
let
bounds =
{ x = Just { min = 0, max = element.width - boxSize }
, y = Nothing
, z = Nothing
}
in
( { model
| trackPx = element.element.width
, animState =
Sub.onResize model.animState <|
Translate.bounds "boxAnim" bounds
>> Translate.bounds "cardAnim" bounds
}
, Cmd.none
)Note: all bounds are pixel values, regardless of the CSS unit used in your builder.
It makes no sense to remap relative units, that's the browser's job, so this function will
only target pixel values. Any non-pixel units are ignored. So if "boxAnim" is using Unit.Cqh
for its axes, any new bounds will be ignored and the animation will continue as before.
📖 For resize strategies and examples, see Responsive Animations.
Subscription animation lifecycle events.
📖 See Event Reference for details.
Opt in to per-frame Progress events.
Off by default.
Progress events can create a lot of noise in your update loop,
especially when debugging the Msg flow in your app. Therefore
they are suppressed by default and you need to explicitly opt in
to receive them.
This is a precedence function, so it can operate as a global setting for all groups in the builder chain, or you can set it on a per-group basis which overrides any global setting for that group.
Sub.withProgressEvents True -- global setting
>> Sub.for "box"
>> Sub.withProgressEvents False -- overrides global for this group
>> ... -- other builders📖 See React for details.
Message type used with update.
import Anim.Engine.Sub as Sub
type Msg
= SubMsg Sub.AnimMsg
| ...Handle animation lifecycle messages.
Returns the updated state and a list of AnimEvents for you to pattern match on.
import Anim.Engine.Sub as Sub
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
SubMsg animMsg ->
let
( animState, events ) =
Sub.update animMsg model.animState
in
( List.foldl handleEvent { model | animState = animState } events
, Cmd.none
)
handleEvent : Sub.AnimEvent -> (Model, Cmd Msg) -> ( Model, Cmd Msg )
handleEvent event (model, cmd) =
case event of
...📖 See Subscriptions for details.
Subscribe to receive animation frame updates.
Your animations will not run without this subscription, and when no animations are running, this subscription is silent and has no performance impact.
import Anim.Engine.Sub as Sub
type Msg
= SubMsg Sub.AnimMsg
| ...
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.subscriptions SubMsg model.animStateTo render an animation, add attributes to the element you want to animate.
📖 See Render for details.
Apply the animation attributes to your element.
import Anim.Engine.Sub as Sub
import Html exposing (div, text)
div
(Sub.attributes "animGroupName" animState)
[ text "Animating element" ]These functions are precedence functions, so they can operate as a global setting for all groups in the builder chain, or you can set them on a per-group basis which overrides any global setting for that group.
Sub.iterations 3 -- global setting
>> Sub.for "box"
>> Sub.iterations 5 -- overrides global for this group
>> ... -- other buildersSet how many times an animation should repeat.
Make an animation loop infinitely.
Make an animation alternate direction on each iteration.
alternate only has a visible effect when the animation runs more than once,
so calling it when iterations is unset or 1 automatically bumps
iterations to 2. An explicit iterations count (or loopForever) set
before or after alternate is preserved.
📖 See Timing for details.
Set the global delay for all animations in this builder.
introAnim : AnimBuilder eng -> AnimBuilder eng
introAnim =
delay 500
>> fadeInHeader
>> slideInSidebar
>> fadeInContentSet the global duration for all animations in this builder.
introAnim : AnimBuilder eng -> AnimBuilder eng
introAnim =
duration 500
>> fadeInHeader
>> slideInSidebar
>> fadeInContentSet the global speed for all animations in this builder.
introAnim : AnimBuilder eng -> AnimBuilder eng
introAnim =
speed 300
>> slideDownHeader
>> slideInSidebar
>> slideUpContent📖 See Easing for details.
Set the global easing function.
heroEntrance : AnimBuilder eng -> AnimBuilder eng
heroEntrance =
easing EaseInOut
>> fadeInHeroTitle
>> slideInHeroArtwork
>> revealPrimaryCtaSet the global spring.
draggableCardSettle : AnimBuilder eng -> AnimBuilder eng
draggableCardSettle =
spring Spring.wobbly
>> settleCardPosition
>> settleCardShadowSet the default length unit for all length-bearing properties.
responsivePanelMotion : AnimBuilder eng -> AnimBuilder eng
responsivePanelMotion =
cssUnit Unit.Vw
>> slidePanelIn
>> growPanelHeightSet the default length unit for the X axis.
responsiveDrawerMotion : AnimBuilder eng -> AnimBuilder eng
responsiveDrawerMotion =
cssUnitX Unit.Vw
>> slideDrawerX
>> alignDrawerLabelXSet the default length unit for the Y axis.
responsiveSheetMotion : AnimBuilder eng -> AnimBuilder eng
responsiveSheetMotion =
cssUnitY Unit.Vh
>> slideSheetY
>> alignSheetHeaderYSet the default length unit for the Z axis.
layeredSceneMotion : AnimBuilder eng -> AnimBuilder eng
layeredSceneMotion =
cssUnitZ Unit.Px
>> pushSceneBackgroundBack
>> bringFloatingCardForwardSet the default length unit used for width values in Sub animations.
responsiveCardWidth : AnimBuilder eng -> AnimBuilder eng
responsiveCardWidth =
cssUnitWidth Unit.Vw
>> growCardWidth
>> settleCardSpacingSet the default length unit used for height values in Sub animations.
responsivePanelHeight : AnimBuilder eng -> AnimBuilder eng
responsivePanelHeight =
cssUnitHeight Unit.Vh
>> expandPanelHeight
>> alignPanelHeaderY📖 See Controlling Animations for details.
Stop a running animation by instantly jumping to its end state.
stop is silent — no Cancelled event is emitted. Cancelled is
reserved for genuine external interruptions (e.g. browser-level
cancellation outside the engine's control).
import Anim.Engine.Sub as Sub
Sub.stop "animGroup" model.animStateReset an animation by instantly jumping back to its start state.
import Anim.Engine.Sub as Sub
Sub.reset "animGroup" model.animStateRestart an animation from the beginning.
import Anim.Engine.Sub as Sub
Sub.restart "animGroup" model.animStatePause a running animation.
import Anim.Engine.Sub as Sub
Sub.pause "animGroup" model.animStateResume a paused animation.
import Anim.Engine.Sub as Sub
Sub.resume "animGroup" model.animState📖 See Discrete Properties for details.
Add a discrete CSS property for entry animations.
The value is applied as an inline style from the first frame and held throughout
the animation. Use this when an element is appearing (e.g., going from
display: none to display: block).
This function is a precedence function, so it can operate as a global setting for all groups in the builder chain, or you can set it on a per-group basis which overrides any global setting for that group.
import Anim.Engine.Sub as Sub
import Anim.Property.Opacity as Opacity
Sub.animate model.animState <|
Sub.discreteEntry "display" "block"
>> Sub.discreteEntry "visibility" "visible"
>> Sub.for "box"
>> Opacity.begin
>> Opacity.to 1
>> Opacity.endAdd a discrete CSS property for exit animations.
Exit animations need to hold their initial state until the very end of the animation, at which point they flip to the final state. Therefore you need to set both entry and exit values for the property.
This function is a precedence function, so it can operate as a global setting for all groups in the builder chain, or you can set it on a per-group basis which overrides any global setting for that group.
Use when an element is disappearing (e.g., going from
display: block to display: none).
import Anim.Engine.Sub as Sub
import Anim.Property.Opacity as Opacity
Sub.animate model.animState <|
Sub.discreteExit "display" "block" "none"
>> Sub.for "box"
>> Opacity.begin
>> Opacity.to 0
>> Opacity.end📖 See Transform Ordering for details.
Set the transform order.
The transform order specifies how translate, rotate, skew and scale transforms
are combined. Start the list with the transform to apply first.
This is a precedence function, so it can operate as a global setting for all groups in the builder chain, or you can set it on a per-group basis which overrides any global setting for that group.
Any missing transforms are automatically appended in the default order
(Translate → Rotate → Skew → Scale).
import Anim.Engine.Sub as Sub
import Anim.Extra.TransformOrder exposing (TransformProperty(..))
Sub.transformOrder [ Scale, Rotate, Translate, Skew ] -- global setting
>> Sub.for "box"
>> Sub.transformOrder [ Rotate, Translate ] -- overrides global for this group
>> ... -- other buildersFreeze an axis to hold it in place while other axes or properties animate.
This is a precedence function, so it can operate as a global setting for all groups in the builder chain, or you can add a per-group freeze on top of the inherited global frozen axes for that group.
Sub.freezeX [ Sub.translate ] -- global setting
>> Sub.for "box"
>> Sub.freezeY [ Sub.rotate ] -- adds to the inherited global freeze for this group
>> ... -- other builders📖 See Interrupting Animations for details.
Freeze the translate property.
Freeze the rotate property.
Freeze the scale property.
Freeze the scale property.
Freeze the X axis of the specified properties at their current animated values.
The named axis indicates which axis will remain frozen while you animate the others.
import Anim.Engine.Sub as Sub
import Anim.Property.Translate as Translate
Sub.animate model.animState <|
Sub.freezeX [ Sub.translate ]
>> Sub.for "box"
>> Translate.begin
>> Translate.toY 0
>> Translate.endFreeze the Y axis of the specified properties at their current animated values.
Freeze the Z axis of the specified properties at their current animated values.
Freeze the X and Y axes of the specified properties at their current animated values.
Freeze the X and Z axes of the specified properties at their current animated values.
Freeze the Y and Z axes of the specified properties at their current animated values.
Freeze all axes of the specified properties at their current animated values.
Unfreeze an axis to allow it to animate.
This is a precedence function, so it can operate as a global setting for all groups in the builder chain, or you can remove axes on a per-group basis from the inherited global frozen axes for that group.
Sub.freezeX [ Sub.translate ] -- global freeze setting
>> Sub.for "box"
>> Sub.unfreezeX [ Sub.translate ] -- removes the inherited global freeze for this group
>> ... -- other buildersUnfreeze the X axis of the specified properties, allowing it to animate again.
Unfreeze the Y axis of the specified properties, allowing it to animate again.
Unfreeze the Z axis of the specified properties, allowing it to animate again.
Unfreeze the X and Y axes of the specified properties.
Unfreeze the X and Z axes of the specified properties.
Unfreeze the Y and Z axes of the specified properties.
Unfreeze all axes of the specified properties.
📖 See State Queries for details.
Check if any animations are currently running.
Returns Nothing if there are no animations.
Check if a specific animation group is currently running.
Returns Nothing if there are no animations for the group.
Check if all animations are complete.
Returns Nothing if there are no animations.
Check if a specific animation group has completed.
Returns Nothing if there are no animations for the group.
Get the current progress of an animation group as a value from 0.0 to 1.0.
Returns Nothing if there are no animations for the group.
import Anim.Engine.Sub as Sub
Sub.getProgress "myAnimation" model.animState
-- Just 0.5 (halfway through)Get the total duration of an animation group, in milliseconds.
This is the wall-clock time for a single iteration - the longest delay + duration
across the group's properties. Looping does not multiply it; each iteration reports
the same span.
Returns Nothing if there are no animations for the group.
import Anim.Engine.Sub as Sub
Sub.getDuration "myAnimation" model.animState
-- Just 600Get the elapsed time of an animation group within its current iteration, in milliseconds.
Counts from the moment the animation is triggered, so it includes any delay before motion
begins. Resets to 0 at each iteration boundary and never exceeds getDuration.
Returns Nothing if there are no animations for the group.
import Anim.Engine.Sub as Sub
Sub.getElapsed "myAnimation" model.animState
-- Just 300 (halfway through a 600ms animation)Get the remaining time of an animation group within its current iteration, in milliseconds.
Equal to getDuration - getElapsed, clamped at 0.
Returns Nothing if there are no animations for the group.
import Anim.Engine.Sub as Sub
Sub.getRemaining "myAnimation" model.animState
-- Just 300 (halfway through a 600ms animation)Get the current interpolated value of a custom property animation.
The second argument is the CSS property name.
Returns Nothing if the element has no animation for the given custom property.
Get the end value of a custom property animation.
The second argument is the CSS property name.
Returns Nothing if the element has no animation for the given custom property.
Get the custom property range (start and end) of an element being animated.
The second argument is the CSS property name.
Returns Nothing if the element has no animation for the given custom property.
Get the start value of a custom property animation.
The second argument is the CSS property name.
Returns Nothing if the element has no animation for the given custom property.
Returns Just 0 if no explicit start value was set, which is the default when no start value is set.
Get the current interpolated value of a custom color property animation.
The second argument is the CSS property name.
Returns Nothing if the element has no animation for the given custom color property.
Get the end value of a custom color property animation.
The second argument is the CSS property name.
Returns Nothing if the element has no animation for the given custom color property.
Get the custom color property range (start and end) of an element being animated.
The second argument is the CSS property name.
Returns Nothing if the element has no animation for the given custom color property.
Get the start value of a custom color property animation.
The second argument is the CSS property name.
Returns Nothing if the element has no animation for the given custom color property.
Returns transparent white (rgba 255 255 255 0) if no explicit start value was set, which is the default when no start value is set.
Get the opacity range (start and end) of an element being animated.
Returns Nothing if the element has no opacity animation.
Get the start opacity of an element being animated.
Returns Nothing if the element has no opacity animation.
Returns Just 1.0 (fully opaque) if no explicit start value was set, which is the default when no start value is set.
Get the end opacity of an element being animated.
Returns Nothing if the element has no opacity animation.
Get the current opacity of an element based on its animation state.
Returns Nothing if the element has no opacity animation.
Returns the start opacity if the animation has not started yet.
Returns the current interpolated opacity if the animation is running.
Returns the end opacity if the animation has completed.
Get the perspective origin range (start and end) of an element being animated.
Returns Nothing if the element has no perspective origin animation.
Get the start perspective origin of an element being animated.
Returns Nothing if the element has no perspective origin animation.
Returns Just { x = 50, y = 50 } if no explicit start value was set, which is the default when no start value is set.
Get the end perspective origin of an element being animated.
Returns Nothing if the element has no perspective origin animation.
Get the current perspective origin of an element based on its animation state.
Returns Nothing if the element has no perspective origin animation.
Returns the start perspective origin if the animation has not started yet.
Returns the current interpolated perspective origin if the animation is running.
Returns the end perspective origin if the animation has completed.
Get the rotate range (start and end) of an element being animated.
Returns Nothing if the element has no rotate animation.
Get the start rotation of an element being animated.
Returns Nothing if the element has no rotate animation.
Returns Just { x = 0, y = 0, z = 0 } if no explicit start value was set, which is the default when no start value is set.
Get the end rotation of an element being animated.
Returns Nothing if the element has no rotate animation.
Get the current rotation of an element based on its animation state.
Returns Nothing if the element has no rotate animation.
Returns the start rotation if the animation has not started yet.
Returns the current interpolated rotation if the animation is running.
Returns the end rotation if the animation has completed.
Get the scale range (start and end) of an element being animated.
Returns Nothing if the element has no scale animation.
Get the start scale of an element being animated.
Returns Nothing if the element has no scale animation.
Returns Just { x = 1, y = 1, z = 1 } if no explicit start value was set, which is the default when no start value is set.
Get the end scale of an element being animated.
Returns Nothing if the element has no scale animation.
Get the current scale of an element based on its animation state.
Returns Nothing if the element has no scale animation.
Returns the start scale if the animation has not started yet.
Returns the current interpolated scale if the animation is running.
Returns the end scale if the animation has completed.
Get the size range (start and end) of an element being animated.
Returns Nothing if the element has no size animation.
Get the start size of an element being animated.
Returns Nothing if the element has no size animation.
Returns Just { width = 0, height = 0 } if no explicit start value was set, which is the default when no start value is set.
Get the end size of an element being animated.
Returns Nothing if the element has no size animation.
Get the current size of an element based on its animation state.
Returns Nothing if the element has no size animation.
Returns the start size if the animation has not started yet.
Returns the current interpolated size if the animation is running.
Returns the end size if the animation has completed.
Get the skew range (start and end) of an element being animated.
Returns Nothing if the element has no skew animation.
Get the start skew of an element being animated.
Returns Nothing if the element has no skew animation.
Get the end skew of an element being animated.
Returns Nothing if the element has no skew animation.
Get the current skew of an element based on its animation state.
Returns Nothing if the element has no skew animation.
Returns the start skew if the animation has not started yet.
Returns the current interpolated skew if the animation is running.
Returns the end skew if the animation has completed.
Get the translate range (start and end) of an element being animated.
Returns Nothing if the element has no translate animation.
Get the start translate of an element being animated.
Returns Nothing if the element has no translate animation.
Returns Just {x = 0, y = 0, z = 0} if no explicit start value was set, which is the default when no start value is set.
Get the end translate of an element being animated.
Returns Nothing if the element has no translate animation.
Get the current translate of an element based on its animation state.
Returns Nothing if the element has no translate animation.
Returns the start translate if the animation has not started yet.
Returns the current interpolated translate if the animation is running.
Returns the end translate if the animation has completed.
Holds the Sub engine state.
Keep this in your model.