Skip to content

Controlling Animations

All animation engines provide control functions to manipulate running animations.

Control Functions

Function Transition Keyframe Sub WAAPI
stop
reset
restart
pause
resume

The Transition Engine has limited control because of CSS itself, not the engine.


Example

Control the ball animation with the buttons.

View Examples

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

import Anim.Builder exposing (AnimBuilder)
import Anim.Engine.Transition as Transition
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 exposing (Easing(..))



-- MAIN


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



-- MODEL


type alias Model =
    { animState : Transition.AnimState }



-- INIT


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


animGroup : String
animGroup =
    "bouncingBall"


{-| Ball size as a percentage of the canvas height (in `cqh` units).
-}
ballSize : Float
ballSize =
    12


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



-- 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
    | Reset


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        Animate ->
            ( { model
                | animState =
                    Transition.animate model.animState <|
                        Transition.for animGroup
                            >> dropBall
              }
            , Cmd.none
            )

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

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



-- 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 Stop, class "ui-action-button warning" ] [ text "⏹️ Stop" ]
            , button [ onClick Reset, class "ui-action-button purple" ] [ text "⏮️ Reset" ]
            ]
        , animationArea model.animState
        ]


animationArea : Transition.AnimState -> Html msg
animationArea animState =
    div
        [ class "example-canvas--fluid"
        , style "border-bottom" "2px solid #333"
        , style "container-type" "size"
        ]
        [ div
            (Transition.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 "🏀" ]
        ]
module Animation.Keyframe.ControllingAnimations.Main exposing (main)

import Anim.Builder exposing (AnimBuilder)
import Anim.Engine.Keyframe as Keyframe
import Anim.Property.Opacity as Opacity
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 exposing (Easing(..))



-- MAIN


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



-- MODEL


type alias Model =
    { animState : Keyframe.AnimState }


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



-- ANIMATION


animGroup : String
animGroup =
    "bouncingBall"


{-| Ball size as a percentage of the canvas height (in `cqh` units).
-}
ballSize : Float
ballSize =
    12


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


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


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



-- UPDATE


type Msg
    = Animate
    | Stop
    | Pause
    | Resume
    | Reset
    | Restart
    | GotAnimMsg Keyframe.AnimMsg


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        Animate ->
            ( { model
                | animState =
                    Keyframe.animate model.animState <|
                        Keyframe.for animGroup
                            >> dropBall
              }
            , Cmd.none
            )

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

        Pause ->
            let
                ( newState, pauseCmd ) =
                    Keyframe.pause animGroup GotAnimMsg model.animState
            in
            ( { model | animState = newState }, pauseCmd )

        Resume ->
            let
                ( newState, resumeCmd ) =
                    Keyframe.resume animGroup GotAnimMsg model.animState
            in
            ( { model | animState = newState }, resumeCmd )

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

        Restart ->
            let
                ( newState, restartCmd ) =
                    Keyframe.restart animGroup GotAnimMsg model.animState
            in
            ( { model | animState = newState }, restartCmd )

        GotAnimMsg _ ->
            ( model, Cmd.none )



-- VIEW


view : Model -> Html Msg
view model =
    div [ class "example-stage" ]
        [ Keyframe.styleNodeFor animGroup model.animState
        , 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 : Keyframe.AnimState -> Html msg
animationArea animState =
    div
        [ class "example-canvas--fluid"
        , style "border-bottom" "2px solid #333"
        , style "container-type" "size"
        ]
        [ div
            (Keyframe.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 "🏀" ]
        ]
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 "🏀" ]
        ]
port module Animation.WAAPI.ControllingAnimations.Main exposing (main)

import Anim.Builder exposing (AnimBuilder)
import Anim.Engine.WAAPI as WAAPI
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 Json.Encode as Encode
import Motion.Easing exposing (Easing(..))



-- PORTS


port motionCmd : Encode.Value -> Cmd msg


port motionMsg : (Encode.Value -> msg) -> Sub msg



-- MAIN


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



-- MODEL


type alias Model =
    { animState : WAAPI.AnimState Msg
    }



-- INIT


init : ( Model, Cmd Msg )
init =
    let
        animState =
            WAAPI.init motionCmd motionMsg <|
                [ Translate.initY animGroup 0 >> Translate.cssUnitY Cqh ]
    in
    ( { animState = animState }
    , Cmd.none
    )



-- ANIMATION


animGroup : String
animGroup =
    "bouncingBall"


{-| Ball size as a percentage of the canvas height (in `cqh` units).
-}
ballSize : Float
ballSize =
    12


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


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
    | GotWaapiMsg WAAPI.AnimMsg


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

        Animate ->
            let
                ( newAnimState, animCmd ) =
                    WAAPI.animate model.animState <|
                        WAAPI.for animGroup
                            >> dropBall
            in
            ( { model | animState = newAnimState }
            , animCmd
            )

        Stop ->
            let
                ( newAnimState, stopCmd ) =
                    WAAPI.stop animGroup model.animState
            in
            ( { model | animState = newAnimState }
            , stopCmd
            )

        Pause ->
            let
                ( newAnimState, pauseCmd ) =
                    WAAPI.pause animGroup model.animState
            in
            ( { model | animState = newAnimState }
            , pauseCmd
            )

        Resume ->
            let
                ( newAnimState, resumeCmd ) =
                    WAAPI.resume animGroup model.animState
            in
            ( { model | animState = newAnimState }
            , resumeCmd
            )

        Reset ->
            let
                ( newAnimState, resetCmd ) =
                    WAAPI.reset animGroup model.animState
            in
            ( { model | animState = newAnimState }
            , resetCmd
            )

        Restart ->
            let
                ( newAnimState, restartCmd ) =
                    WAAPI.restart animGroup model.animState
            in
            ( { model | animState = newAnimState }
            , restartCmd
            )



-- SUBSCRIPTIONS


subscriptions : Model -> Sub Msg
subscriptions model =
    WAAPI.subscriptions GotWaapiMsg 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 : WAAPI.AnimState msg -> Html msg
animationArea animState =
    div
        [ class "example-canvas--fluid"
        , style "border-bottom" "2px solid #333"
        , style "container-type" "size"
        ]
        [ div
            (WAAPI.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 "🏀" ]
        ]

Control Functions

All control functions take an animation group name and the current AnimState, returning the updated state, and sometimes a Cmd msg.

Stop

Immediately jumps to the animation's end state and stops playback.

View Source Code
        Stop ->
            ( { model | animState = Transition.stop animGroup model.animState }
            , Cmd.none
            )
        Stop ->
            ( { model | animState = Keyframe.stop animGroup model.animState }
            , Cmd.none
            )
        Stop ->
            ( { model | animState = Sub.stop animGroup model.animState }
            , Cmd.none
            )
        Stop ->
            let
                ( newAnimState, stopCmd ) =
                    WAAPI.stop animGroup model.animState
            in
            ( { model | animState = newAnimState }
            , stopCmd
            )

Reset

Immediately jumps back to the animation's start state and stops.

View Source Code
        Reset ->
            ( { model | animState = Transition.reset animGroup model.animState }
            , Cmd.none
            )
        Reset ->
            ( { model | animState = Keyframe.reset animGroup model.animState }
            , Cmd.none
            )
        Reset ->
            ( { model | animState = Sub.reset animGroup model.animState }
            , Cmd.none
            )
        Reset ->
            let
                ( newAnimState, resetCmd ) =
                    WAAPI.reset animGroup model.animState
            in
            ( { model | animState = newAnimState }
            , resetCmd
            )

Restart

Resets to the start state, then immediately begins playing the animation again.

View Source Code
        Restart ->
            let
                ( newState, restartCmd ) =
                    Keyframe.restart animGroup GotAnimMsg model.animState
            in
            ( { model | animState = newState }, restartCmd )
        Restart ->
            ( { model | animState = Sub.restart animGroup model.animState }
            , Cmd.none
            )
        Restart ->
            let
                ( newAnimState, restartCmd ) =
                    WAAPI.restart animGroup model.animState
            in
            ( { model | animState = newAnimState }
            , restartCmd
            )

Transition doesn't support restart.

Pause

Freezes the animation at its current position. The animation can be resumed later.

View Source Code
        Pause ->
            let
                ( newState, pauseCmd ) =
                    Keyframe.pause animGroup GotAnimMsg model.animState
            in
            ( { model | animState = newState }, pauseCmd )
        Pause ->
            ( { model | animState = Sub.pause animGroup model.animState }
            , Cmd.none
            )
        Pause ->
            let
                ( newAnimState, pauseCmd ) =
                    WAAPI.pause animGroup model.animState
            in
            ( { model | animState = newAnimState }
            , pauseCmd
            )

Transition doesn't support pause.

Resume

Continues a paused animation from exactly where it was frozen.

View Source Code
        Resume ->
            let
                ( newState, resumeCmd ) =
                    Keyframe.resume animGroup GotAnimMsg model.animState
            in
            ( { model | animState = newState }, resumeCmd )
        Resume ->
            ( { model | animState = Sub.resume animGroup model.animState }
            , Cmd.none
            )
        Resume ->
            let
                ( newAnimState, resumeCmd ) =
                    WAAPI.resume animGroup model.animState
            in
            ( { model | animState = newAnimState }
            , resumeCmd
            )

Transition doesn't support resume.


Next Steps

Learn about Timing for animations.

Timing →