Skip to content

Sub Engine

This page is a practical guide to using the Sub engine. Read Engines Overview when you want side-by-side comparisons and tradeoffs.

The Sub Engine uses Elm subscriptions to update animation state on every frame. This provides full programmatic control over animations, including mid-flight queries and mid-flight redirections.

Example

Animation control - use the buttons to control the bouncing ball animation.

View Example

View Source Code
module Animation.Sub.ControllingAnimations.Main exposing (main)

import Anim.Builder exposing (AnimBuilder)
import Anim.Engine.Sub as Sub
import Anim.Property.Translate as Translate
import Anim.Unit exposing (Unit(..))
import Browser
import Html exposing (Html, button, div, text)
import Html.Attributes exposing (class, style)
import Html.Events exposing (onClick)
import Motion.Easing as Easing exposing (Easing(..))



-- MAIN


main : Program () Model Msg
main =
    Browser.element
        { init = \_ -> init
        , view = view
        , update = update
        , subscriptions = subscriptions
        }



-- MODEL


type alias Model =
    { animState : Sub.AnimState }


animGroup : String
animGroup =
    "bouncingBall"


ballSize : Float
ballSize =
    12


ballSizeCqh : String
ballSizeCqh =
    String.fromFloat ballSize ++ "cqh"



-- INIT


init : ( Model, Cmd Msg )
init =
    ( { animState =
            Sub.init
                [ Translate.initY animGroup 0 >> Translate.cssUnitY Cqh ]
      }
    , Cmd.none
    )



-- ANIMATION


dropBall : AnimBuilder { eng | withTiming : () } -> AnimBuilder { eng | withTiming : () }
dropBall =
    Translate.begin
        >> Translate.fromY 0
        >> Translate.toY (100 - ballSize)
        >> Translate.speed 75
        >> Translate.easing BounceOut
        >> Translate.end



-- UPDATE


type Msg
    = Animate
    | Stop
    | Pause
    | Resume
    | Reset
    | Restart
    | GotSubMsg Sub.AnimMsg


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        GotSubMsg subMsg ->
            let
                ( newAnimState, _ ) =
                    Sub.update subMsg model.animState
            in
            ( { model | animState = newAnimState }
            , Cmd.none
            )

        Animate ->
            ( { model
                | animState =
                    Sub.animate model.animState <|
                        Sub.for animGroup
                            >> dropBall
              }
            , Cmd.none
            )

        Stop ->
            ( { model | animState = Sub.stop animGroup model.animState }
            , Cmd.none
            )

        Pause ->
            ( { model | animState = Sub.pause animGroup model.animState }
            , Cmd.none
            )

        Resume ->
            ( { model | animState = Sub.resume animGroup model.animState }
            , Cmd.none
            )

        Reset ->
            ( { model | animState = Sub.reset animGroup model.animState }
            , Cmd.none
            )

        Restart ->
            ( { model | animState = Sub.restart animGroup model.animState }
            , Cmd.none
            )



-- SUBSCRIPTIONS


subscriptions : Model -> Sub Msg
subscriptions model =
    Sub.subscriptions GotSubMsg model.animState



-- VIEW


view : Model -> Html Msg
view model =
    div [ class "example-stage" ]
        [ div [ class "example-controls" ]
            [ button [ onClick Animate, class "ui-action-button primary" ] [ text "🏀 Animate" ]
            , button [ onClick Pause, class "ui-action-button success" ] [ text "⏸️ Pause" ]
            , button [ onClick Resume, class "ui-action-button success" ] [ text "▶️ Resume" ]
            , button [ onClick Stop, class "ui-action-button warning" ] [ text "⏹️ Stop" ]
            , button [ onClick Reset, class "ui-action-button purple" ] [ text "⏮️ Reset" ]
            , button [ onClick Restart, class "ui-action-button purple" ] [ text "🔄 Restart" ]
            ]
        , animationArea model.animState
        ]


animationArea : Sub.AnimState -> Html msg
animationArea animState =
    div
        [ class "example-canvas--fluid"
        , style "border-bottom" "2px solid #333"
        , style "container-type" "size"
        ]
        [ div
            (Sub.attributes animGroup animState
                ++ [ style "position" "absolute"
                   , style "left" ("calc(50% - " ++ String.fromFloat (ballSize / 2) ++ "cqh)")
                   , style "width" ballSizeCqh
                   , style "height" ballSizeCqh
                   , style "font-size" ballSizeCqh
                   , style "line-height" ballSizeCqh
                   ]
            )
            [ text "🏀" ]
        ]

Quick Walkthrough

Here's a general workflow to get up an running quickly.

1. Build

View Source Code
import Anim.Engine.Sub as Sub
import Anim.Property.Opacity as Opacity


fadeIn : Sub.AnimBuilder eng -> Sub.AnimBuilder eng
fadeIn =
    Opacity.begin
        >> Opacity.to 1
        >> Opacity.end

2. Initialize

View Source Code
type alias Model =
    { animState : Sub.AnimState }


init : ( Model, Cmd Msg )
init =
    ( { animState = Sub.init [ Opacity.init "card" 0 ] }
    , Cmd.none
    )

3. Render

Render animation attributes on the element being animated.

View Source Code
view : Model -> Html Msg
view model =
    div []
        [ button [ onClick TriggerFadeIn ] [ text "Fade In" ]
        , div (Sub.attributes "card" model.animState) [ text "Animated card" ]
        ]

4. Trigger with animate

Call animate to apply the animation config to the current AnimState.

View Source Code
TriggerFadeIn ->
    ( { model | animState = 
        Sub.animate model.animState <|
            Sub.for "card"
                >> fadeIn
      }
    , Cmd.none
    )

5. React

Use update for incoming Sub events.

View Source Code
type Msg
    = TriggerFadeIn
    | GotAnimMsg Sub.AnimMsg

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        GotAnimMsg animMsg ->
            let
                ( animState, events ) =
                    Sub.update animMsg model.animState
            in
            ( List.foldl handleAnimEvent { model | animState = animState } events
            , Cmd.none 
            )

        _ ->
            ( model, Cmd.none )


handleAnimEvent : Sub.AnimEvent -> Model -> ( Model, Cmd Msg )
handleAnimEvent event model =
    case event of
        Sub.Ended "card" ->
            ( model, Cmd.none )

        _ ->
            ( model, Cmd.none )

subscriptions : Model -> Sub Msg
subscriptions model =
    Sub.subscriptions GotAnimMsg model.animState

In Detail

Initialize

Pass a list of property initializers to init. Each registers an animation group name and sets the element's starting inline style from the first render.

View Source Code
init : ( Model, Cmd Msg )
init =
    ( { animState = Sub.init [ Opacity.init "ball" 0 ] }
    , Cmd.none
    )

📖 See Initialize for more info.

Trigger

Call animate to apply an animation to the current AnimState.

View Source Code
TriggerDrop ->
    ( { model | animState = 
        Sub.animate model.animState <|
            Sub.for "card"
                >> dropBall
      }
    , Cmd.none
    )

📖 See Triggering Animations for more info.

Mid-Flight Interruptions

Sub keeps frame-by-frame runtime state, so interrupting with a new animation continues smoothly from the current in-flight position.

📖 See Interrupting Animations for more info.

OnLoad Animations

For on-load animations, trigger animate when the page initializes, the animation runs immediately.

Update

Use update to process incoming animation messages. It returns the updated AnimState and a list of any events that occurred during that frame.

View Source Code
GotAnimMsg animMsg ->
    let
        ( animState, events ) =
            Sub.update animMsg model.animState
    in
    List.foldl handleAnimEvent ( { model | animState = animState }, Cmd.none ) events

Events

update returns a List AnimEvent per call — multiple events can occur in a single frame. Use List.foldl to process them.

Every event carries the animation group name. Some events carry an additional value:

  • Cancelled and Paused include the progress at the moment of cancellation/pause (Float, 0.0–1.0)
  • Iteration includes the iteration count (Int)
  • Progress fires every frame with the current progress (Float, 0.0–1.0)
View Source Code
handleAnimEvent : AnimEvent -> ( Model, Cmd Msg ) -> ( Model, Cmd Msg )
handleAnimEvent event ( model, cmd ) =
    case event of
        Started "ball" ->
            ( model, cmd )

        Ended "ball" ->
            ( model, cmd )

        Cancelled "ball" _ ->
            ( model, cmd )

        Progress "ball" progress ->
            ( { model | ballProgress = progress }, cmd )

        _ ->
            ( model, cmd )
Event Fires when...
Started Animation begins playing
Ended Animation completes
Cancelled Animation is interrupted by something outside the engine's control.
Iteration Each iteration completes (looping or alternating)
Progress Every frame while the animation is running
Paused pause is called on a running animation
Resumed resume is called on a paused animation
Restarted restart is called

Subscriptions

The Sub engine requires a subscription to receive animation frame updates. The subscription is dormant when no animations are active.

View Source Code
subscriptions : Model -> Sub Msg
subscriptions model =
    Sub.subscriptions GotAnimMsg model.animState

📖 See React for more info.

View

Apply attributes to the animated element to apply the current inline styles on each frame.

View Source Code
div (Sub.attributes "ball" model.animState) [ text "Ball" ]

📖 See Render for more info.

Responsive Strategy

Use relative CSS units whenever the motion can be defined in layout-relative terms.

For measured pixel targets, Sub supports proportional remap for resize updates. Therefore:

  • On resize, update bounds with onResize and Translate.bounds / Scale.bounds / PerspectiveOrigin.bounds.
  • Running animations remap to the equivalent relative position inside the updated bounds.
  • Idle animations also re-position proportionally inside the updated bounds.

📖 See Responsive Animations for more info.

Playback

Set iterations, loopForever, and alternate in the animation builder.

View Source Code
spinForever =
    Sub.loopForever
        >> Sub.alternate
        >> Sub.for "icon"
        >> Rotate.begin
        >> Rotate.toZ 360
        >> Rotate.duration 1000
        >> Rotate.end

📖 See Playback for the full looping, iterations, and alternate API with live examples.

Timing

Set the default duration, speed, and delay. Inherited by every property that doesn't override them.

  • duration — animation length in milliseconds.
  • speed — alternative to duration; set a rate in property units per second.
  • delay — wait before the animation begins, in milliseconds.
View Source Code
fadeIn =
    Sub.delay 500
        >> Sub.duration 800
        >> Sub.for "card"
        >> Opacity.begin
        >> Opacity.to 1
        >> Opacity.end

📖 See Timing for more info.

Easing

Sub animations evaluate the easing curve using the elm-community/easing-functions library on each frame — no pre-sampled approximations.

Set the default easing for all properties that don't override it:

View Source Code
fadeIn =
    Sub.easing CubicInOut
        >> Sub.for "card"
        >> Opacity.begin
        >> Opacity.to 1
        >> Opacity.duration 300
        >> Opacity.delay 50
        >> Opacity.end

📖 See Easing for all available easing functions.

Spring

Sub animations evaluate the spring's analytic solution every frame — exact damped-harmonic-oscillator motion with no sampling.

Set the default spring for all properties that don't override it: The motion ends when each value has settled at the target — there is no explicit duration.

View Source Code
bouncyReveal =
    Sub.spring Spring.wobbly
        >> Sub.for "card"
        >> Opacity.begin
        >> Opacity.to 1
        >> Opacity.end

📖 See Spring for the full preset list and tuning guidance.

Controls

Function Description
stop Jump to end state
reset Jump to start state
restart Reset and begin playing again
pause Freeze at current position
resume Continue from paused position
View Source Code
Stop ->
    ( { model | animState = Sub.stop "card" model.animState }, Cmd.none )

Reset ->
    ( { model | animState = Sub.reset "card" model.animState }, Cmd.none )
Restart ->
    ( { model | animState = Sub.restart "card" model.animState }, Cmd.none )

Pause ->
    ( { model | animState = Sub.pause "card" model.animState }, Cmd.none )
Resume ->
    ( { model | animState = Sub.resume "card" model.animState }, Cmd.none )

📖 See Controlling Animations for more info.

Discrete Properties

The Sub engine manages discrete properties as inline styles. discreteEntry values are applied from the first animation frame, and discreteExit values flip on the last frame. No additional view setup is needed.

📖 See Discrete Properties for the full API, live examples, and source code.

Transform Order

Use transformOrder to set the order in which transform properties are applied.

Call it after for to set the current animation group's order. Call it before selecting a group to set the global default for groups that do not override it.

View Source Code
import Anim.Extra.TransformOrder exposing (TransformProperty(..))

animateBox =
    Sub.for "box"
        >> Sub.transformOrder [ Scale, Rotate, Translate ]
        >> Translate.begin
        >> ...

📖 See Transform Order for full details.

Freeze Axes

Freeze one or more transform axes so they stop updating during subsequent frames, while the rest of the animation continues.

View Source Code
Sub.animate model.animState <|
    Sub.freezeX [ Sub.translate ]
        >> Sub.for "box"
        >> Translate.begin
        >> Translate.toY 0
        >> Translate.end

📖 See Freezing Axes with freeze* for more info.

State Queries

Query animation state.

View Source Code
Sub.anyRunning model.animState           -- Maybe Bool
Sub.isRunning "ball" model.animState     -- Maybe Bool
Sub.allComplete model.animState          -- Maybe Bool
Sub.isComplete "ball" model.animState    -- Maybe Bool
Sub.isCancelled "ball" model.animState   -- Maybe Bool
Sub.getProgress "ball" model.animState   -- Maybe Float (0.0–1.0)

Nothing is returned when no animation exists for the given group.

Property Queries

Because the Sub engine updates on every frame, current values are always accessible.

All query functions follow the same pattern:

  • get[Property]Start, and return Maybe [PropertyValue].
  • get[Property]Current, and return Maybe [PropertyValue].
  • get[Property]End, and return Maybe [PropertyValue]
View Source Code
Sub.getOpacityStart "ball" model.animState      -- Maybe Float
Sub.getOpacityCurrent "ball" model.animState    -- Maybe Float
Sub.getOpacityEnd "ball" model.animState        -- Maybe Float
Sub.getTranslateStart "ball" model.animState    -- Maybe { x, y, z }
Sub.getTranslateCurrent "ball" model.animState  -- Maybe { x, y, z }
Sub.getTranslateEnd "ball" model.animState      -- Maybe { x, y, z }

Nothing is returned when no animation exists for the given group.

When to Choose This Engine

Choose Sub when you want maximum Elm-side control with per-frame updates and current-value access.

  • Best for: gameplay-style interactions, simulation-like motion, and logic that reacts continuously to animation progress.
  • Avoid when: you prefer browser-native animation execution with less Elm runtime work.

API Quick Reference

Types

Type Description
AnimState Tracks animations and their states
AnimBuilder eng Carries all animation configurations
AnimMsg Messages from animation frame subscription
AnimEvent Events returned by update
AnimGroupName String type alias for the animation group name
TransformProperty Custom transform ordering
FreezeProperty Axis to freeze (Translate, Rotate, Scale, Skew)

Initialize

Function Type Description
init List (AnimBuilder eng -> AnimBuilder eng) -> AnimState Create initial animation state

Trigger

Function Type Description
animate AnimState -> (AnimBuilder eng -> AnimBuilder eng) -> AnimState Apply an animation to the current state

Events

Event Description
Started AnimGroupName Animation begins playing
Ended AnimGroupName Animation completes
Cancelled AnimGroupName Float Animation cancelled; Float is progress at cancellation
Restarted AnimGroupName Animation is restarted
Paused AnimGroupName Float Animation paused; Float is progress at pause
Resumed AnimGroupName Animation resumed
Iteration AnimGroupName Int Loop iteration completes; Int is iteration count
Progress AnimGroupName Float Each frame; Float is current progress (0.0–1.0)

Update

Function Type Description
update AnimMsg -> AnimState -> ( AnimState, List AnimEvent ) Process messages and return events

Subscriptions

Function Type Description
subscriptions (AnimMsg -> msg) -> AnimState -> Sub msg Animation frame subscription

View

Function Type Description
attributes AnimGroupName -> AnimState -> List (Html.Attribute msg) Get animation attributes for an element

Playback

Function Type Description
iterations Int -> AnimBuilder eng -> AnimBuilder eng Set number of iterations
loopForever AnimBuilder eng -> AnimBuilder eng Loop animation infinitely
alternate AnimBuilder eng -> AnimBuilder eng Reverse direction on each iteration

Timing

Function Type Description
duration Int -> AnimBuilder eng -> AnimBuilder eng Set duration (ms)
speed Float -> AnimBuilder eng -> AnimBuilder eng Set speed (property units/sec)
delay Int -> AnimBuilder eng -> AnimBuilder eng Set delay before animation starts (ms)

Easing

Function Type Description
easing Easing -> AnimBuilder eng -> AnimBuilder eng Set easing function

Spring

Function Type Description
spring Spring -> AnimBuilder eng -> AnimBuilder eng Set spring physics

Controls

Function Type Description
stop AnimGroupName -> AnimState -> AnimState Jump to end state and stop
reset AnimGroupName -> AnimState -> AnimState Jump to start state and stop
restart AnimGroupName -> AnimState -> AnimState Reset and begin playing again
pause AnimGroupName -> AnimState -> AnimState Freeze at current position
resume AnimGroupName -> AnimState -> AnimState Continue from paused position

Discrete Properties

Function Type Description
discreteEntry String -> String -> AnimBuilder eng -> AnimBuilder eng Set a CSS property value when the animation starts
discreteExit String -> String -> String -> AnimBuilder eng -> AnimBuilder eng Set a CSS property value during and after the animation

Transform Order

Function Type Description
transformOrder List TransformProperty -> AnimBuilder eng -> AnimBuilder eng Set custom transform order

Freeze Axes

Function Type Description
freezeX AnimGroupName -> List FreezeProperty -> AnimState -> AnimState Freeze the X axis
freezeY AnimGroupName -> List FreezeProperty -> AnimState -> AnimState Freeze the Y axis
freezeZ AnimGroupName -> List FreezeProperty -> AnimState -> AnimState Freeze the Z axis
freezeXY AnimGroupName -> List FreezeProperty -> AnimState -> AnimState Freeze the X and Y axes
freezeXZ AnimGroupName -> List FreezeProperty -> AnimState -> AnimState Freeze the X and Z axes
freezeYZ AnimGroupName -> List FreezeProperty -> AnimState -> AnimState Freeze the Y and Z axes
freezeXYZ AnimGroupName -> List FreezeProperty -> AnimState -> AnimState Freeze all axes
unfreezeX AnimGroupName -> List FreezeProperty -> AnimState -> AnimState Unfreeze the X axis
unfreezeY AnimGroupName -> List FreezeProperty -> AnimState -> AnimState Unfreeze the Y axis
unfreezeZ AnimGroupName -> List FreezeProperty -> AnimState -> AnimState Unfreeze the Z axis
unfreezeXY AnimGroupName -> List FreezeProperty -> AnimState -> AnimState Unfreeze the X and Y axes
unfreezeXZ AnimGroupName -> List FreezeProperty -> AnimState -> AnimState Unfreeze the X and Z axes
unfreezeYZ AnimGroupName -> List FreezeProperty -> AnimState -> AnimState Unfreeze the Y and Z axes
unfreezeXYZ AnimGroupName -> List FreezeProperty -> AnimState -> AnimState Unfreeze all axes

State Queries

Function Type Description
anyRunning AnimState -> Maybe Bool Check if any animation is running
isRunning AnimGroupName -> AnimState -> Maybe Bool Check if a specific group is animating
allComplete AnimState -> Maybe Bool Check if all animations are complete
isComplete AnimGroupName -> AnimState -> Maybe Bool Check if a specific group's animation is complete
isCancelled AnimGroupName -> AnimState -> Maybe Bool Check if a specific group's animation was cancelled
getProgress AnimGroupName -> AnimState -> Maybe Float Get current progress (0.0–1.0)

Property Queries

Function Type Description
getOpacityStart AnimGroupName -> AnimState -> Maybe Float Get start opacity
getOpacityEnd AnimGroupName -> AnimState -> Maybe Float Get end opacity
getOpacityCurrent AnimGroupName -> AnimState -> Maybe Float Get current opacity
getTranslateStart AnimGroupName -> AnimState -> Maybe { x, y, z } Get start translate
getTranslateEnd AnimGroupName -> AnimState -> Maybe { x, y, z } Get end translate
getTranslateCurrent AnimGroupName -> AnimState -> Maybe { x, y, z } Get current translate
getRotateStart AnimGroupName -> AnimState -> Maybe { x, y, z } Get start rotate
getRotateEnd AnimGroupName -> AnimState -> Maybe { x, y, z } Get end rotate
getRotateCurrent AnimGroupName -> AnimState -> Maybe { x, y, z } Get current rotate
getScaleStart AnimGroupName -> AnimState -> Maybe { x, y, z } Get start scale
getScaleEnd AnimGroupName -> AnimState -> Maybe { x, y, z } Get end scale
getScaleCurrent AnimGroupName -> AnimState -> Maybe { x, y, z } Get current scale
getSizeStart AnimGroupName -> AnimState -> Maybe { width, height } Get start size
getSizeEnd AnimGroupName -> AnimState -> Maybe { width, height } Get end size
getSizeCurrent AnimGroupName -> AnimState -> Maybe { width, height } Get current size
getSkewStart AnimGroupName -> AnimState -> Maybe { x, y } Get start skew
getSkewEnd AnimGroupName -> AnimState -> Maybe { x, y } Get end skew
getSkewCurrent AnimGroupName -> AnimState -> Maybe { x, y } Get current skew
getPropertyStart AnimGroupName -> String -> AnimState -> Maybe Float Get start value for a custom numeric property
getPropertyEnd AnimGroupName -> String -> AnimState -> Maybe Float Get end value for a custom numeric property
getPropertyCurrent AnimGroupName -> String -> AnimState -> Maybe Float Get current value for a custom numeric property
getColorPropertyStart AnimGroupName -> String -> AnimState -> Maybe Color Get start value for a custom color property
getColorPropertyEnd AnimGroupName -> String -> AnimState -> Maybe Color Get end value for a custom color property
getColorPropertyCurrent AnimGroupName -> String -> AnimState -> Maybe Color Get current value for a custom color property

Nothing is returned when no animation exists for the given group.

For complete API details, see the Anim.Engine.Sub documentation.

Next Steps

The WAAPI Engine provides all of the features of the Transition, Keyframe, and Sub engines combined, with native browser control.

WAAPI Engine →