Skip to content

Mid-Flight Interruptions

When an animation is running on an element and you trigger another animation on the same element, the result depends on the engine and the properties involved.

Engine Summary

Engine Same Property Multiple Properties
Transition ✅ Redirects from current position ✅ Run side by side
Keyframe ❌ Jumps to new animation start ❌ Replaces all properties
Sub ✅ Redirects from current position ✅ Run side by side
WAAPI ✅ Redirects from current position ✅ Run side by side

Single Property

Scenario: A property is animating on an element, and you trigger another animation for the same property on the same element.

The following example uses a CustomColor property (BackgroundColor) to demonstrate the behaviour for each Engine. Click a button to change the background color of the box; the color change will take 3 seconds, click another color button before the change is complete to redirect to a new color.

View Examples

Behaviour: Smooth redirect from current mid-flight value to new end target value

Behaviour: The new @keyframes rules for the animation replace the existing rules.

📖 See: Keyframe Engine — Interrupting Animations for details.

Behaviour: Smooth redirect from current mid-flight value to new end target value

Behaviour: Smooth redirect from current mid-flight value to new end target value

View Source Code
module Animation.Keyframe.InterruptingAnimations.SingleProperty.Main exposing (main)

import Anim.Builder exposing (AnimBuilder, ForKeyframe)
import Anim.Engine.Keyframe as Keyframe
import Anim.Extra.Color as Color exposing (Color)
import Anim.Property.CustomColor as CustomColor
import Browser
import Html exposing (Html, 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
        , update = update
        , view = view
        , subscriptions = always Sub.none
        }



-- MODEL


animGroupName : String
animGroupName =
    "movingBox"


type alias Model =
    { animState : Keyframe.AnimState
    }


init : ( Model, Cmd Msg )
init =
    ( { animState =
            Keyframe.init
                [ CustomColor.init animGroupName CustomColor.BackgroundColor <|
                    Color.rgb 118 118 118
                ]
      }
    , Cmd.none
    )



-- ANIMATIONS


color1 : Color
color1 =
    Color.rgb 255 87 51


color2 : Color
color2 =
    Color.rgb 40 167 69


color3 : Color
color3 =
    Color.rgb 111 66 193


color4 : Color
color4 =
    Color.rgb 255 193 7


toColor1 : Keyframe.EngineBuilder -> Keyframe.EngineBuilder
toColor1 =
    colorBox (CustomColor.to color1)


toColor2 : Keyframe.EngineBuilder -> Keyframe.EngineBuilder
toColor2 =
    colorBox (CustomColor.to color2)


toColor3 : Keyframe.EngineBuilder -> Keyframe.EngineBuilder
toColor3 =
    colorBox (CustomColor.to color3)


toColor4 : Keyframe.EngineBuilder -> Keyframe.EngineBuilder
toColor4 =
    colorBox (CustomColor.to color4)


colorBox : (CustomColor.Builder ForKeyframe -> CustomColor.Builder ForKeyframe) -> (Keyframe.EngineBuilder -> Keyframe.EngineBuilder)
colorBox moveFunc =
    CustomColor.begin CustomColor.BackgroundColor
        >> moveFunc
        >> CustomColor.duration 3000
        >> CustomColor.easing Linear
        >> CustomColor.end



-- UPDATE


type Msg
    = GotAnimationUpdate Keyframe.AnimMsg
    | Color1
    | Color2
    | Color3
    | Color4


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

        Color1 ->
            ( { model
                | animState =
                    Keyframe.animate model.animState <|
                        Keyframe.for animGroupName
                            >> toColor1
              }
            , Cmd.none
            )

        Color2 ->
            ( { model
                | animState =
                    Keyframe.animate model.animState <|
                        Keyframe.for animGroupName
                            >> toColor2
              }
            , Cmd.none
            )

        Color3 ->
            ( { model
                | animState =
                    Keyframe.animate model.animState <|
                        Keyframe.for animGroupName
                            >> toColor3
              }
            , Cmd.none
            )

        Color4 ->
            ( { model
                | animState =
                    Keyframe.animate model.animState <|
                        Keyframe.for animGroupName
                            >> toColor4
              }
            , Cmd.none
            )



-- VIEW


view : Model -> Html Msg
view model =
    let
        button bgColor label onClickMsg =
            Html.button
                [ onClick onClickMsg
                , class "ui-action-button"
                , style "background-color" <|
                    Color.toHex bgColor
                ]
                [ text label ]

        color1Button =
            button color1 "Color 1" Color1

        color2Button =
            button color2 "Color 2" Color2

        color3Button =
            button color3 "Color 3" Color3

        color4Button =
            button color4 "Color 4" Color4
    in
    div
        [ class "example-stage"
        , style "text-align" "center"
        ]
        [ Keyframe.styleNode model.animState
        , div [ class "example-controls" ]
            [ color1Button
            , color2Button
            , color3Button
            , color4Button
            ]
        , div
            (Keyframe.attributes animGroupName model.animState
                ++ [ class "example-canvas" ]
            )
            []
        ]
module Animation.Transition.InterruptingAnimations.SingleProperty.Main exposing (main)

import Anim.Builder exposing (AnimBuilder, ForTransition)
import Anim.Engine.Transition as Transition
import Anim.Extra.Color as Color exposing (Color)
import Anim.Property.CustomColor as CustomColor
import Browser
import Html exposing (Html, 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
        , update = update
        , view = view
        , subscriptions = always Sub.none
        }



-- MODEL


animGroupName : String
animGroupName =
    "movingBox"


type alias Model =
    { animState : Transition.AnimState }


init : ( Model, Cmd Msg )
init =
    ( { animState =
            Transition.init
                [ CustomColor.init animGroupName CustomColor.BackgroundColor <|
                    Color.rgb 118 118 118
                ]
      }
    , Cmd.none
    )



-- ANIMATIONS


color1 : Color
color1 =
    Color.rgb 255 87 51


color2 : Color
color2 =
    Color.rgb 40 167 69


color3 : Color
color3 =
    Color.rgb 111 66 193


color4 : Color
color4 =
    Color.rgb 255 193 7


toColor1 : Transition.EngineBuilder -> Transition.EngineBuilder
toColor1 =
    colorBox (CustomColor.to color1)


toColor2 : Transition.EngineBuilder -> Transition.EngineBuilder
toColor2 =
    colorBox (CustomColor.to color2)


toColor3 : Transition.EngineBuilder -> Transition.EngineBuilder
toColor3 =
    colorBox (CustomColor.to color3)


toColor4 : Transition.EngineBuilder -> Transition.EngineBuilder
toColor4 =
    colorBox (CustomColor.to color4)


colorBox : (CustomColor.Builder ForTransition -> CustomColor.Builder ForTransition) -> Transition.EngineBuilder -> Transition.EngineBuilder
colorBox moveFunc =
    CustomColor.begin CustomColor.BackgroundColor
        >> moveFunc
        >> CustomColor.duration 3000
        >> CustomColor.easing Linear
        >> CustomColor.end



-- UPDATE


type Msg
    = GotAnimationUpdate Transition.AnimMsg
    | Color1
    | Color2
    | Color3
    | Color4


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

        Color1 ->
            ( { model
                | animState =
                    Transition.animate model.animState <|
                        Transition.for animGroupName
                            >> toColor1
              }
            , Cmd.none
            )

        Color2 ->
            ( { model
                | animState =
                    Transition.animate model.animState <|
                        Transition.for animGroupName
                            >> toColor2
              }
            , Cmd.none
            )

        Color3 ->
            ( { model
                | animState =
                    Transition.animate model.animState <|
                        Transition.for animGroupName
                            >> toColor3
              }
            , Cmd.none
            )

        Color4 ->
            ( { model
                | animState =
                    Transition.animate model.animState <|
                        Transition.for animGroupName
                            >> toColor4
              }
            , Cmd.none
            )



-- VIEW


view : Model -> Html Msg
view model =
    let
        button bgColor label onClickMsg =
            Html.button
                [ onClick onClickMsg
                , class "ui-action-button"
                , style "background-color" <|
                    Color.toHex bgColor
                ]
                [ text label ]

        color1Button =
            button color1 "Color 1" Color1

        color2Button =
            button color2 "Color 2" Color2

        color3Button =
            button color3 "Color 3" Color3

        color4Button =
            button color4 "Color 4" Color4
    in
    div [ class "example-stage" ]
        [ div [ class "example-controls" ]
            [ color1Button
            , color2Button
            , color3Button
            , color4Button
            ]
        , div
            (Transition.attributes animGroupName model.animState
                ++ [ class "example-canvas" ]
            )
            []
        ]
module Animation.Sub.InterruptingAnimations.SingleProperty.Main exposing (main)

import Anim.Builder exposing (AnimBuilder, ForSub)
import Anim.Engine.Sub as Sub
import Anim.Extra.Color as Color exposing (Color)
import Anim.Property.CustomColor as CustomColor
import Browser
import Html exposing (Html, 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
        , update = update
        , view = view
        , subscriptions = subscriptions
        }



-- MODEL


animGroupName : String
animGroupName =
    "movingBox"


type alias Model =
    { animState : Sub.AnimState
    }


init : ( Model, Cmd Msg )
init =
    ( { animState =
            Sub.init
                [ CustomColor.init animGroupName CustomColor.BackgroundColor <|
                    Color.rgb 118 118 118
                ]
      }
    , Cmd.none
    )



-- ANIMATIONS


color1 : Color
color1 =
    Color.rgb 255 87 51


color2 : Color
color2 =
    Color.rgb 40 167 69


color3 : Color
color3 =
    Color.rgb 111 66 193


color4 : Color
color4 =
    Color.rgb 255 193 7


toColor1 : Sub.EngineBuilder -> Sub.EngineBuilder
toColor1 =
    colorBox (CustomColor.to color1)


toColor2 : Sub.EngineBuilder -> Sub.EngineBuilder
toColor2 =
    colorBox (CustomColor.to color2)


toColor3 : Sub.EngineBuilder -> Sub.EngineBuilder
toColor3 =
    colorBox (CustomColor.to color3)


toColor4 : Sub.EngineBuilder -> Sub.EngineBuilder
toColor4 =
    colorBox (CustomColor.to color4)


colorBox : (CustomColor.Builder ForSub -> CustomColor.Builder ForSub) -> Sub.EngineBuilder -> Sub.EngineBuilder
colorBox moveFunc =
    CustomColor.begin CustomColor.BackgroundColor
        >> moveFunc
        >> CustomColor.duration 3000
        >> CustomColor.easing Linear
        >> CustomColor.end



-- UPDATE


type Msg
    = GotAnimationUpdate Sub.AnimMsg
    | Color1
    | Color2
    | Color3
    | Color4


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

        Color1 ->
            ( { model
                | animState =
                    Sub.animate model.animState <|
                        Sub.for animGroupName
                            >> toColor1
              }
            , Cmd.none
            )

        Color2 ->
            ( { model
                | animState =
                    Sub.animate model.animState <|
                        Sub.for animGroupName
                            >> toColor2
              }
            , Cmd.none
            )

        Color3 ->
            ( { model
                | animState =
                    Sub.animate model.animState <|
                        Sub.for animGroupName
                            >> toColor3
              }
            , Cmd.none
            )

        Color4 ->
            ( { model
                | animState =
                    Sub.animate model.animState <|
                        Sub.for animGroupName
                            >> toColor4
              }
            , Cmd.none
            )



-- SUBSCRIPTIONS


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



-- VIEW


view : Model -> Html Msg
view model =
    let
        button bgColor label onClickMsg =
            Html.button
                [ onClick onClickMsg
                , class "ui-action-button"
                , style "background-color" <|
                    Color.toHex bgColor
                ]
                [ text label ]

        color1Button =
            button color1 "Color 1" Color1

        color2Button =
            button color2 "Color 2" Color2

        color3Button =
            button color3 "Color 3" Color3

        color4Button =
            button color4 "Color 4" Color4
    in
    div
        [ class "example-stage"
        , style "text-align" "center"
        ]
        [ div [ class "example-controls" ]
            [ color1Button
            , color2Button
            , color3Button
            , color4Button
            ]
        , div
            (Sub.attributes animGroupName model.animState
                ++ [ class "example-canvas" ]
            )
            []
        ]
port module Animation.WAAPI.InterruptingAnimations.SingleProperty.Main exposing (main)

import Anim.Builder exposing (AnimBuilder, ForWAAPI)
import Anim.Engine.WAAPI as WAAPI
import Anim.Extra.Color as Color exposing (Color)
import Anim.Property.CustomColor as CustomColor
import Browser
import Html exposing (Html, div, text)
import Html.Attributes exposing (class, style)
import Html.Events exposing (onClick)
import Json.Encode as Encode
import Motion.Easing as 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
        , update = update
        , view = view
        , subscriptions = subscriptions
        }



-- MODEL


animGroupName : String
animGroupName =
    "colorBox"


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


init : ( Model, Cmd Msg )
init =
    ( { animState =
            WAAPI.init motionCmd motionMsg <|
                [ CustomColor.init animGroupName CustomColor.BackgroundColor <|
                    Color.rgb 118 118 118
                ]
      }
    , Cmd.none
    )



-- ANIMATIONS


color1 : Color
color1 =
    Color.rgb 255 87 51


color2 : Color
color2 =
    Color.rgb 40 167 69


color3 : Color
color3 =
    Color.rgb 111 66 193


color4 : Color
color4 =
    Color.rgb 255 193 7


toColor1 : WAAPI.EngineBuilder -> WAAPI.EngineBuilder
toColor1 =
    colorBox (CustomColor.to color1)


toColor2 : WAAPI.EngineBuilder -> WAAPI.EngineBuilder
toColor2 =
    colorBox (CustomColor.to color2)


toColor3 : WAAPI.EngineBuilder -> WAAPI.EngineBuilder
toColor3 =
    colorBox (CustomColor.to color3)


toColor4 : WAAPI.EngineBuilder -> WAAPI.EngineBuilder
toColor4 =
    colorBox (CustomColor.to color4)


colorBox : (CustomColor.Builder ForWAAPI -> CustomColor.Builder ForWAAPI) -> WAAPI.EngineBuilder -> WAAPI.EngineBuilder
colorBox moveFunc =
    CustomColor.begin CustomColor.BackgroundColor
        >> moveFunc
        >> CustomColor.duration 3000
        >> CustomColor.easing Linear
        >> CustomColor.end



-- UPDATE


type Msg
    = GotAnimationUpdate WAAPI.AnimMsg
    | Color1
    | Color2
    | Color3
    | Color4


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

        Color1 ->
            let
                ( newAnimState, cmd ) =
                    WAAPI.animate model.animState <|
                        WAAPI.for animGroupName
                            >> toColor1
            in
            ( { model | animState = newAnimState }
            , cmd
            )

        Color2 ->
            let
                ( newAnimState, cmd ) =
                    WAAPI.animate model.animState <|
                        WAAPI.for animGroupName
                            >> toColor2
            in
            ( { model | animState = newAnimState }
            , cmd
            )

        Color3 ->
            let
                ( newAnimState, cmd ) =
                    WAAPI.animate model.animState <|
                        WAAPI.for animGroupName
                            >> toColor3
            in
            ( { model | animState = newAnimState }
            , cmd
            )

        Color4 ->
            let
                ( newAnimState, cmd ) =
                    WAAPI.animate model.animState <|
                        WAAPI.for animGroupName
                            >> toColor4
            in
            ( { model | animState = newAnimState }
            , cmd
            )



-- SUBSCRIPTIONS


subscriptions : Model -> Sub Msg
subscriptions model =
    WAAPI.subscriptions GotAnimationUpdate model.animState



-- VIEW


view : Model -> Html Msg
view model =
    let
        button bgColor label onClickMsg =
            Html.button
                [ onClick onClickMsg
                , class "ui-action-button"
                , style "background-color" <|
                    Color.toHex bgColor
                ]
                [ text label ]

        color1Button =
            button color1 "Color 1" Color1

        color2Button =
            button color2 "Color 2" Color2

        color3Button =
            button color3 "Color 3" Color3

        color4Button =
            button color4 "Color 4" Color4
    in
    div
        [ class "example-stage"
        , style "text-align" "center"
        ]
        [ div [ class "example-controls" ]
            [ color1Button
            , color2Button
            , color3Button
            , color4Button
            ]
        , div
            (WAAPI.attributes animGroupName model.animState
                ++ [ class "example-canvas" ]
            )
            []
        ]

Multiple Properties

Adding Properties Mid-Flight

Scenario: One property is animating, and another is added mid-flight.

The following example uses Translate and a CustomColor property (BackgroundColor) to demonstrate the behaviour. Click either move button, then a color button before the move is complete to see the behaviour of the Engine.

View Examples

Behaviour: Translate and CustomColor (BackgroundColor) run independently side by side.

Behaviour: The new @keyframes rules for the animation replace the existing rules.

📖 See: Keyframe Engine — Interrupting Animations for details.

Behaviour: Translate and CustomColor (BackgroundColor) run independently side by side.

Behaviour: Translate and CustomColor (BackgroundColor) run independently side by side.

View Source Code
module Animation.Keyframe.InterruptingAnimations.MultipleProperties.Main exposing (main)

import Anim.Builder exposing (AnimBuilder)
import Anim.Engine.Keyframe as Keyframe
import Anim.Extra.Color as Color exposing (Color)
import Anim.Property.CustomColor as CustomColor
import Anim.Property.Translate as Translate
import Anim.Unit exposing (Unit(..))
import Browser
import Html exposing (Html, 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
        , update = update
        , view = view
        , subscriptions = \_ -> Sub.none
        }



-- MODEL


type alias Model =
    { animState : Keyframe.AnimState }


animGroupName : String
animGroupName =
    "movingBox"


{-| Box size expressed as a percentage of the canvas width. The box uses
`boxPct cqw` for both width and height so it always stays square, and
`Translate.toX` targets are
in `cqw` units, so the left, center and right anchors all scale with the
canvas. The box is vertically centered via CSS (`top: calc(50% - half box)`)
so no Y animation is needed - one less moving part and zero Elm-side
resize plumbing.
-}
boxPct : Float
boxPct =
    12


centerXCqw : Float
centerXCqw =
    (100 - boxPct) / 2


boxHalfPct : Float
boxHalfPct =
    boxPct / 2


init : ( Model, Cmd Msg )
init =
    ( { animState =
            Keyframe.init
                [ Translate.initX animGroupName centerXCqw >> Translate.cssUnit Cqw
                , CustomColor.init animGroupName CustomColor.BackgroundColor <| Color.rgb 118 118 118
                ]
      }
    , Cmd.none
    )



-- POSITION HELPERS


type XPos
    = XLeft
    | XCenter
    | XRight


targetX : XPos -> Float
targetX pos =
    case pos of
        XLeft ->
            0

        XCenter ->
            centerXCqw

        XRight ->
            100 - boxPct



-- COLORS


color1 : Color
color1 =
    Color.rgb 255 87 51


color2 : Color
color2 =
    Color.rgb 40 167 69


color3 : Color
color3 =
    Color.rgb 111 66 193


color4 : Color
color4 =
    Color.rgb 255 193 7



-- ANIMATIONS


moveBoxX : Float -> AnimBuilder { eng | withTiming : () } -> AnimBuilder { eng | withTiming : () }
moveBoxX x =
    Translate.begin
        >> Translate.toX x
        >> Translate.speed 25
        >> Translate.easing BounceOut
        >> Translate.end


changeColor : Color -> AnimBuilder { eng | withTiming : () } -> AnimBuilder { eng | withTiming : () }
changeColor color =
    CustomColor.begin CustomColor.BackgroundColor
        >> CustomColor.to color
        >> CustomColor.duration 3000
        >> CustomColor.easing Linear
        >> CustomColor.end



-- UPDATE


type Msg
    = GotAnimationUpdate Keyframe.AnimMsg
    | MoveLeft
    | MoveRight
    | ChangeColor Color


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

        MoveLeft ->
            ( { model
                | animState =
                    Keyframe.animate model.animState <|
                        Keyframe.for animGroupName
                            >> moveBoxX (targetX XLeft)
              }
            , Cmd.none
            )

        MoveRight ->
            ( { model
                | animState =
                    Keyframe.animate model.animState <|
                        Keyframe.for animGroupName
                            >> moveBoxX (targetX XRight)
              }
            , Cmd.none
            )

        ChangeColor color ->
            ( { model
                | animState =
                    Keyframe.animate model.animState <|
                        Keyframe.for animGroupName
                            >> changeColor color
              }
            , Cmd.none
            )



-- VIEW


view : Model -> Html Msg
view model =
    let
        posButton bgColor label onClickMsg =
            Html.button
                [ onClick onClickMsg
                , class "ui-action-button"
                , style "background-color" bgColor
                ]
                [ text label ]

        colorButton color label =
            Html.button
                [ onClick (ChangeColor color)
                , class "ui-action-button"
                , style "background-color" (Color.toHex color)
                ]
                [ text label ]
    in
    div [ class "example-stage" ]
        [ Keyframe.styleNode model.animState
        , div [ class "example-controls" ]
            [ posButton "#333" "Move Left" MoveLeft
            , posButton "#333" "Move Right" MoveRight
            ]
        , div [ class "example-controls" ]
            [ colorButton color1 "Color 1"
            , colorButton color2 "Color 2"
            , colorButton color3 "Color 3"
            , colorButton color4 "Color 4"
            ]
        , div
            [ class "example-canvas--fluid"
            , style "container-type" "size"
            ]
            [ div
                (Keyframe.attributes animGroupName model.animState
                    ++ Keyframe.events GotAnimationUpdate
                    ++ [ style "width" (String.fromFloat boxPct ++ "cqw")
                       , style "height" (String.fromFloat boxPct ++ "cqw")
                       , style "position" "absolute"
                       , style "top" ("calc(50% - " ++ String.fromFloat boxHalfPct ++ "cqw)")
                       , style "left" "0"
                       , style "border-radius" "8px"
                       ]
                )
                []
            ]
        ]
module Animation.Transition.InterruptingAnimations.MultipleProperties.Main exposing (main)

import Anim.Builder exposing (AnimBuilder)
import Anim.Engine.Transition as Transition
import Anim.Extra.Color as Color exposing (Color)
import Anim.Property.CustomColor as CustomColor
import Anim.Property.Translate as Translate
import Anim.Unit exposing (Unit(..))
import Browser
import Html exposing (Html, 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
        , update = update
        , view = view
        , subscriptions = \_ -> Sub.none
        }



-- MODEL


type alias Model =
    { animState : Transition.AnimState }


animGroupName : String
animGroupName =
    "movingBox"


{-| Box size expressed as a percentage of the canvas width. The box uses
`boxPct cqw` for both width and height so it always stays square, and
`Translate.toX` targets are
in `cqw` units, so the left, center and right anchors all scale with the
canvas. The box is vertically centered via CSS (`top: calc(50% - half box)`)
so no Y animation is needed - one less moving part and zero Elm-side
resize plumbing.
-}
boxPct : Float
boxPct =
    12


centerXCqw : Float
centerXCqw =
    (100 - boxPct) / 2


boxHalfPct : Float
boxHalfPct =
    boxPct / 2


init : ( Model, Cmd Msg )
init =
    ( { animState =
            Transition.init
                [ Translate.initX animGroupName centerXCqw >> Translate.cssUnitX Cqw
                , CustomColor.init animGroupName CustomColor.BackgroundColor <| Color.rgb 118 118 118
                ]
      }
    , Cmd.none
    )



-- POSITION HELPERS


type XPos
    = XLeft
    | XCenter
    | XRight


targetX : XPos -> Float
targetX pos =
    case pos of
        XLeft ->
            0

        XCenter ->
            centerXCqw

        XRight ->
            100 - boxPct



-- COLORS


color1 : Color
color1 =
    Color.rgb 255 87 51


color2 : Color
color2 =
    Color.rgb 40 167 69


color3 : Color
color3 =
    Color.rgb 111 66 193


color4 : Color
color4 =
    Color.rgb 255 193 7



-- ANIMATIONS


moveBoxX : Float -> AnimBuilder { eng | withTiming : () } -> AnimBuilder { eng | withTiming : () }
moveBoxX x =
    Translate.begin
        >> Translate.toX x
        >> Translate.speed 25
        >> Translate.easing BounceOut
        >> Translate.end


changeColor : Color -> AnimBuilder { eng | withTiming : () } -> AnimBuilder { eng | withTiming : () }
changeColor color =
    CustomColor.begin CustomColor.BackgroundColor
        >> CustomColor.to color
        >> CustomColor.duration 3000
        >> CustomColor.easing Linear
        >> CustomColor.end



-- UPDATE


type Msg
    = GotAnimationUpdate Transition.AnimMsg
    | MoveLeft
    | MoveRight
    | ChangeColor Color


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

        MoveLeft ->
            ( { model
                | animState =
                    Transition.animate model.animState <|
                        Transition.for animGroupName
                            >> moveBoxX (targetX XLeft)
              }
            , Cmd.none
            )

        MoveRight ->
            ( { model
                | animState =
                    Transition.animate model.animState <|
                        Transition.for animGroupName
                            >> moveBoxX (targetX XRight)
              }
            , Cmd.none
            )

        ChangeColor color ->
            ( { model
                | animState =
                    Transition.animate model.animState <|
                        Transition.for animGroupName
                            >> changeColor color
              }
            , Cmd.none
            )



-- VIEW


view : Model -> Html Msg
view model =
    let
        posButton bgColor label onClickMsg =
            Html.button
                [ onClick onClickMsg
                , class "ui-action-button"
                , style "background-color" bgColor
                ]
                [ text label ]

        colorButton color label =
            Html.button
                [ onClick (ChangeColor color)
                , class "ui-action-button"
                , style "background-color" (Color.toHex color)
                ]
                [ text label ]
    in
    div [ class "example-stage" ]
        [ div [ class "example-controls" ]
            [ posButton "#333" "Move Left" MoveLeft
            , posButton "#333" "Move Right" MoveRight
            ]
        , div [ class "example-controls" ]
            [ colorButton color1 "Color 1"
            , colorButton color2 "Color 2"
            , colorButton color3 "Color 3"
            , colorButton color4 "Color 4"
            ]
        , div
            [ class "example-canvas--fluid"
            , style "container-type" "size"
            ]
            [ div
                (Transition.attributes animGroupName model.animState
                    ++ Transition.events GotAnimationUpdate
                    ++ [ style "width" (String.fromFloat boxPct ++ "cqw")
                       , style "height" (String.fromFloat boxPct ++ "cqw")
                       , style "position" "absolute"
                       , style "top" ("calc(50% - " ++ String.fromFloat boxHalfPct ++ "cqw)")
                       , style "left" "0"
                       , style "border-radius" "8px"
                       ]
                )
                []
            ]
        ]
module Animation.Sub.InterruptingAnimations.MultipleProperties.Main exposing (..)

import Anim.Builder exposing (AnimBuilder)
import Anim.Engine.Sub as Sub
import Anim.Extra.Color as Color exposing (Color)
import Anim.Property.CustomColor as CustomColor
import Anim.Property.Translate as Translate
import Anim.Unit exposing (Unit(..))
import Browser
import Html exposing (Html, 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
        , update = update
        , view = view
        , subscriptions = subscriptions
        }



-- MODEL


type alias Model =
    { animState : Sub.AnimState }


type XPos
    = XLeft
    | XCenter
    | XRight


animGroupName : String
animGroupName =
    "movingBox"


boxPct : Float
boxPct =
    12


centerXCqw : Float
centerXCqw =
    (100 - boxPct) / 2


boxHalfPct : Float
boxHalfPct =
    boxPct / 2


init : ( Model, Cmd Msg )
init =
    ( { animState =
            Sub.init
                [ Translate.initX animGroupName centerXCqw
                    >> Translate.cssUnitX Cqw
                , CustomColor.init animGroupName CustomColor.BackgroundColor <| Color.rgb 118 118 118
                ]
      }
    , Cmd.none
    )



-- POSITION HELPERS


targetX : XPos -> Float
targetX pos =
    case pos of
        XLeft ->
            0

        XCenter ->
            centerXCqw

        XRight ->
            100 - boxPct



-- COLORS


color1 : Color
color1 =
    Color.rgb 255 87 51


color2 : Color
color2 =
    Color.rgb 40 167 69


color3 : Color
color3 =
    Color.rgb 111 66 193


color4 : Color
color4 =
    Color.rgb 255 193 7



-- ANIMATIONS


moveBoxX : Float -> AnimBuilder { eng | withTiming : () } -> AnimBuilder { eng | withTiming : () }
moveBoxX x =
    Translate.begin
        >> Translate.toX x
        >> Translate.speed 25
        >> Translate.easing BounceOut
        >> Translate.end


changeColor : Color -> AnimBuilder { eng | withTiming : () } -> AnimBuilder { eng | withTiming : () }
changeColor color =
    CustomColor.begin CustomColor.BackgroundColor
        >> CustomColor.to color
        >> CustomColor.duration 3000
        >> CustomColor.easing Linear
        >> CustomColor.end



-- UPDATE


type Msg
    = GotAnimationUpdate Sub.AnimMsg
    | MoveLeft
    | MoveRight
    | ChangeColor Color


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

        MoveLeft ->
            ( { model
                | animState =
                    Sub.animate model.animState <|
                        Sub.for animGroupName
                            >> moveBoxX (targetX XLeft)
              }
            , Cmd.none
            )

        MoveRight ->
            ( { model
                | animState =
                    Sub.animate model.animState <|
                        Sub.for animGroupName
                            >> moveBoxX (targetX XRight)
              }
            , Cmd.none
            )

        ChangeColor color ->
            ( { model
                | animState =
                    Sub.animate model.animState <|
                        Sub.for animGroupName
                            >> changeColor color
              }
            , Cmd.none
            )



-- SUBSCRIPTIONS


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



-- VIEW


view : Model -> Html Msg
view model =
    let
        posButton bgColor label onClickMsg =
            Html.button
                [ onClick onClickMsg
                , class "ui-action-button"
                , style "background-color" bgColor
                ]
                [ text label ]

        colorButton color label =
            Html.button
                [ onClick (ChangeColor color)
                , class "ui-action-button"
                , style "background-color" (Color.toHex color)
                ]
                [ text label ]
    in
    div [ class "example-stage" ]
        [ div [ class "example-controls" ]
            [ posButton "#333" "Move Left" MoveLeft
            , posButton "#333" "Move Right" MoveRight
            ]
        , div [ class "example-controls" ]
            [ colorButton color1 "Color 1"
            , colorButton color2 "Color 2"
            , colorButton color3 "Color 3"
            , colorButton color4 "Color 4"
            ]
        , div
            [ class "example-canvas--fluid"
            , style "container-type" "size"
            ]
            [ div
                (Sub.attributes animGroupName model.animState
                    ++ [ style "width" (String.fromFloat boxPct ++ "cqw")
                       , style "height" (String.fromFloat boxPct ++ "cqw")
                       , style "position" "absolute"
                       , style "top" ("calc(50% - " ++ String.fromFloat boxHalfPct ++ "cqw)")
                       , style "left" "0"
                       , style "border-radius" "8px"
                       ]
                )
                []
            ]
        ]
port module Animation.WAAPI.InterruptingAnimations.MultipleProperties.Main exposing (main)

import Anim.Builder exposing (AnimBuilder)
import Anim.Engine.WAAPI as WAAPI
import Anim.Extra.Color as Color exposing (Color)
import Anim.Property.CustomColor as CustomColor
import Anim.Property.Translate as Translate
import Anim.Unit exposing (Unit(..))
import Browser
import Html exposing (Html, div, text)
import Html.Attributes exposing (class, style)
import Html.Events exposing (onClick)
import Json.Encode as Encode
import Motion.Easing exposing (Easing(..))



-- MAIN


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



-- PORTS


port motionCmd : Encode.Value -> Cmd msg


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



-- MODEL


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


animGroupName : String
animGroupName =
    "movingBox"


{-| Box size expressed as a percentage of the canvas width. The box uses
`boxPct cqw` for both width and height so it always stays square, and
`Translate.toX` targets are
in `cqw` units, so the left, center and right anchors all scale with the
canvas. The box is vertically centered via CSS (`top: calc(50% - half box)`)
so no Y animation is needed - one less moving part and zero Elm-side
resize plumbing.
-}
boxPct : Float
boxPct =
    12


centerXCqw : Float
centerXCqw =
    (100 - boxPct) / 2


boxHalfPct : Float
boxHalfPct =
    boxPct / 2


init : ( Model, Cmd Msg )
init =
    ( { animState =
            WAAPI.init motionCmd motionMsg <|
                [ Translate.initX animGroupName centerXCqw >> Translate.cssUnitX Cqw
                , CustomColor.init animGroupName CustomColor.BackgroundColor <| Color.rgb 118 118 118
                ]
      }
    , Cmd.none
    )



-- POSITION HELPERS


type XPos
    = XLeft
    | XCenter
    | XRight


targetX : XPos -> Float
targetX pos =
    case pos of
        XLeft ->
            0

        XCenter ->
            centerXCqw

        XRight ->
            100 - boxPct



-- COLORS


color1 : Color
color1 =
    Color.rgb 255 87 51


color2 : Color
color2 =
    Color.rgb 40 167 69


color3 : Color
color3 =
    Color.rgb 111 66 193


color4 : Color
color4 =
    Color.rgb 255 193 7



-- ANIMATIONS


moveBoxX : Float -> AnimBuilder { eng | withTiming : () } -> AnimBuilder { eng | withTiming : () }
moveBoxX x =
    Translate.begin
        >> Translate.toX x
        >> Translate.speed 25
        >> Translate.easing BounceOut
        >> Translate.end


changeColor : Color -> AnimBuilder { eng | withTiming : () } -> AnimBuilder { eng | withTiming : () }
changeColor color =
    CustomColor.begin CustomColor.BackgroundColor
        >> CustomColor.to color
        >> CustomColor.duration 3000
        >> CustomColor.easing Linear
        >> CustomColor.end



-- UPDATE


type Msg
    = GotAnimationUpdate WAAPI.AnimMsg
    | MoveLeft
    | MoveRight
    | ChangeColor Color


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

        MoveLeft ->
            let
                ( newAnimState, cmd ) =
                    WAAPI.animate model.animState <|
                        WAAPI.for animGroupName
                            >> moveBoxX (targetX XLeft)
            in
            ( { model | animState = newAnimState }, cmd )

        MoveRight ->
            let
                ( newAnimState, cmd ) =
                    WAAPI.animate model.animState <|
                        WAAPI.for animGroupName
                            >> moveBoxX (targetX XRight)
            in
            ( { model | animState = newAnimState }, cmd )

        ChangeColor color ->
            let
                ( newAnimState, cmd ) =
                    WAAPI.animate model.animState <|
                        WAAPI.for animGroupName
                            >> changeColor color
            in
            ( { model | animState = newAnimState }, cmd )



-- SUBSCRIPTIONS


subscriptions : Model -> Sub.Sub Msg
subscriptions model =
    WAAPI.subscriptions GotAnimationUpdate model.animState



-- VIEW


view : Model -> Html Msg
view model =
    let
        posButton bgColor label onClickMsg =
            Html.button
                [ onClick onClickMsg
                , class "ui-action-button"
                , style "background-color" bgColor
                ]
                [ text label ]

        colorButton color label =
            Html.button
                [ onClick (ChangeColor color)
                , class "ui-action-button"
                , style "background-color" (Color.toHex color)
                ]
                [ text label ]
    in
    div [ class "example-stage" ]
        [ div [ class "example-controls" ]
            [ posButton "#333" "Move Left" MoveLeft
            , posButton "#333" "Move Right" MoveRight
            ]
        , div [ class "example-controls" ]
            [ colorButton color1 "Color 1"
            , colorButton color2 "Color 2"
            , colorButton color3 "Color 3"
            , colorButton color4 "Color 4"
            ]
        , div
            [ class "example-canvas--fluid"
            , style "container-type" "size"
            ]
            [ div
                (WAAPI.attributes animGroupName model.animState
                    ++ [ style "width" (String.fromFloat boxPct ++ "cqw")
                       , style "height" (String.fromFloat boxPct ++ "cqw")
                       , style "position" "absolute"
                       , style "top" ("calc(50% - " ++ String.fromFloat boxHalfPct ++ "cqw)")
                       , style "left" "0"
                       , style "border-radius" "8px"
                       ]
                )
                []
            ]
        ]

Properties with Multiple Axes

Scenario: Animating one axis, then animating another axis before it completes.

The following example uses the Translate property to demonstrate the behaviour. Click 'Move Right' followed by 'Move Up' before the animation completes to see the behaviour of the Engine.

View Examples

Behaviour: Smooth redirect from current position

Behaviour: The new @keyframes rules for the animation replace the existing rules.

📖 See: Keyframe Engine — Interrupting Animations for details.

Behaviour: Seamless interruption to new target

Behaviour: Seamless interruption to new target

View Source Code
module Animation.Keyframe.InterruptingAnimations.MultipleAxes.Main exposing (main)

import Anim.Builder exposing (AnimBuilder, ForKeyframe)
import Anim.Engine.Keyframe as Keyframe
import Anim.Property.Translate as Translate
import Anim.Unit exposing (Unit(..))
import Browser
import Html exposing (Html, 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
        , update = update
        , view = view
        , subscriptions = \_ -> Sub.none
        }



-- MODEL


animGroupName : String
animGroupName =
    "movingBox"


type alias Model =
    { animState : Keyframe.AnimState }


{-| Box size expressed as a percentage of the canvas on each axis.
The box width is `boxPct cqw`, height is `boxPct cqh`, and translate
targets are expressed in the matching axis-unit. Everything scales
with the canvas - no Elm-side resize handling required.
-}
boxPct : Float
boxPct =
    12


init : ( Model, Cmd Msg )
init =
    ( { animState =
            Keyframe.init
                [ Translate.initXY animGroupName (targetX XCenter) (targetY YCenter) >> Translate.cssUnitX Cqw >> Translate.cssUnitY Cqh
                ]
      }
    , Cmd.none
    )



-- POSITION HELPERS


type XPos
    = XLeft
    | XCenter
    | XRight


type YPos
    = YTop
    | YCenter
    | YBottom


{-| X target in `cqw` (0..100 - boxPct).
-}
targetX : XPos -> Float
targetX pos =
    case pos of
        XLeft ->
            0

        XCenter ->
            (100 - boxPct) / 2

        XRight ->
            100 - boxPct


{-| Y target in `cqh` (0..100 - boxPct).
-}
targetY : YPos -> Float
targetY pos =
    case pos of
        YTop ->
            0

        YCenter ->
            (100 - boxPct) / 2

        YBottom ->
            100 - boxPct



-- ANIMATIONS


moveBoxX : Float -> Keyframe.EngineBuilder -> Keyframe.EngineBuilder
moveBoxX x =
    moveBox (Translate.toX x)


moveBoxY : Float -> Keyframe.EngineBuilder -> Keyframe.EngineBuilder
moveBoxY y =
    moveBox (Translate.toY y)


moveBox : (Translate.Builder ForKeyframe -> Translate.Builder ForKeyframe) -> Keyframe.EngineBuilder -> Keyframe.EngineBuilder
moveBox moveFunc =
    Translate.begin
        >> moveFunc
        >> Translate.speed 25
        >> Translate.easing QuintOut
        >> Translate.end



-- UPDATE


type Msg
    = GotAnimationUpdate Keyframe.AnimMsg
    | MoveLeft
    | MoveRight
    | MoveUp
    | MoveDown


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

        MoveLeft ->
            ( { model
                | animState =
                    Keyframe.animate model.animState <|
                        Keyframe.for animGroupName
                            >> moveBoxX (targetX XLeft)
              }
            , Cmd.none
            )

        MoveRight ->
            ( { model
                | animState =
                    Keyframe.animate model.animState <|
                        Keyframe.for animGroupName
                            >> moveBoxX (targetX XRight)
              }
            , Cmd.none
            )

        MoveUp ->
            ( { model
                | animState =
                    Keyframe.animate model.animState <|
                        Keyframe.for animGroupName
                            >> moveBoxY (targetY YTop)
              }
            , Cmd.none
            )

        MoveDown ->
            ( { model
                | animState =
                    Keyframe.animate model.animState <|
                        Keyframe.for animGroupName
                            >> moveBoxY (targetY YBottom)
              }
            , Cmd.none
            )



-- VIEW


view : Model -> Html Msg
view model =
    let
        button bgColor label onClickMsg =
            Html.button
                [ onClick onClickMsg
                , class "ui-action-button"
                , style "background-color" bgColor
                ]
                [ text label ]

        moveLeftButton =
            button "#007BFF" "Move Left" MoveLeft

        moveRightButton =
            button "#28A745" "Move Right" MoveRight

        moveUpButton =
            button "#6F42C1" "Move Up" MoveUp

        moveDownButton =
            button "#FFC107" "Move Down" MoveDown

        box =
            div
                (Keyframe.attributes animGroupName model.animState
                    ++ Keyframe.events GotAnimationUpdate
                    ++ [ style "width" (String.fromFloat boxPct ++ "cqw")
                       , style "height" (String.fromFloat boxPct ++ "cqh")
                       , style "background-color" "#FF5733"
                       , style "border-radius" "8px"
                       , style "position" "absolute"
                       , style "top" "0"
                       , style "left" "0"
                       ]
                )
                []
    in
    div [ class "example-stage" ]
        [ Keyframe.styleNode model.animState
        , div [ class "example-controls" ]
            [ moveLeftButton
            , moveRightButton
            , moveUpButton
            , moveDownButton
            ]
        , div
            [ class "example-canvas--fluid"
            , style "container-type" "size"
            ]
            [ box ]
        ]
module Animation.Transition.InterruptingAnimations.MultipleAxes.Main exposing (main)

import Anim.Builder exposing (AnimBuilder, ForTransition)
import Anim.Engine.Transition as Transition
import Anim.Property.Translate as Translate
import Anim.Unit exposing (Unit(..))
import Browser
import Html exposing (Html, 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
        , update = update
        , view = view
        , subscriptions = \_ -> Sub.none
        }



-- MODEL


animGroupName : String
animGroupName =
    "movingBox"


type alias Model =
    { animState : Transition.AnimState }


{-| Box size expressed as a percentage of the canvas on each axis.
The box width is `boxPct cqw`, height is `boxPct cqh`, and translate
targets are expressed in the matching axis-unit. Everything scales
with the canvas - no Elm-side resize handling required.
-}
boxPct : Float
boxPct =
    12


centerXCqw : Float
centerXCqw =
    (100 - boxPct) / 2


centerYCqh : Float
centerYCqh =
    (100 - boxPct) / 2


init : ( Model, Cmd Msg )
init =
    ( { animState =
            Transition.init
                [ Translate.initXY animGroupName centerXCqw centerYCqh >> Translate.cssUnitX Cqw >> Translate.cssUnitY Cqh
                ]
      }
    , Cmd.none
    )



-- POSITION HELPERS


type XPos
    = XLeft
    | XCenter
    | XRight


type YPos
    = YTop
    | YCenter
    | YBottom


{-| X target in `cqw` (0..100 - boxPct).
-}
targetX : XPos -> Float
targetX pos =
    case pos of
        XLeft ->
            0

        XCenter ->
            (100 - boxPct) / 2

        XRight ->
            100 - boxPct


{-| Y target in `cqh` (0..100 - boxPct).
-}
targetY : YPos -> Float
targetY pos =
    case pos of
        YTop ->
            0

        YCenter ->
            (100 - boxPct) / 2

        YBottom ->
            100 - boxPct



-- ANIMATIONS


moveBoxX : Float -> Transition.EngineBuilder -> Transition.EngineBuilder
moveBoxX x =
    moveBox (Translate.toX x)


moveBoxY : Float -> Transition.EngineBuilder -> Transition.EngineBuilder
moveBoxY y =
    moveBox (Translate.toY y)


moveBox : (Translate.Builder ForTransition -> Translate.Builder ForTransition) -> Transition.EngineBuilder -> Transition.EngineBuilder
moveBox moveFunc =
    Translate.begin
        >> moveFunc
        >> Translate.speed 25
        >> Translate.easing QuintOut
        >> Translate.end



-- UPDATE


type Msg
    = GotAnimationUpdate Transition.AnimMsg
    | MoveLeft
    | MoveRight
    | MoveUp
    | MoveDown


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

        MoveLeft ->
            ( { model
                | animState =
                    Transition.animate model.animState <|
                        Transition.for animGroupName
                            >> moveBoxX (targetX XLeft)
              }
            , Cmd.none
            )

        MoveRight ->
            ( { model
                | animState =
                    Transition.animate model.animState <|
                        Transition.for animGroupName
                            >> moveBoxX (targetX XRight)
              }
            , Cmd.none
            )

        MoveUp ->
            ( { model
                | animState =
                    Transition.animate model.animState <|
                        Transition.for animGroupName
                            >> moveBoxY (targetY YTop)
              }
            , Cmd.none
            )

        MoveDown ->
            ( { model
                | animState =
                    Transition.animate model.animState <|
                        Transition.for animGroupName
                            >> moveBoxY (targetY YBottom)
              }
            , Cmd.none
            )



-- VIEW


view : Model -> Html Msg
view model =
    let
        button bgColor label onClickMsg =
            Html.button
                [ onClick onClickMsg
                , class "ui-action-button"
                , style "background-color" bgColor
                ]
                [ text label ]

        moveLeftButton =
            button "#007BFF" "Move Left" MoveLeft

        moveRightButton =
            button "#28A745" "Move Right" MoveRight

        moveUpButton =
            button "#6F42C1" "Move Up" MoveUp

        moveDownButton =
            button "#FFC107" "Move Down" MoveDown

        box =
            div
                (Transition.attributes animGroupName model.animState
                    ++ Transition.events GotAnimationUpdate
                    ++ [ style "width" (String.fromFloat boxPct ++ "cqw")
                       , style "height" (String.fromFloat boxPct ++ "cqh")
                       , style "background-color" "#FF5733"
                       , style "border-radius" "8px"
                       , style "position" "absolute"
                       , style "top" "0"
                       , style "left" "0"
                       ]
                )
                []
    in
    div [ class "example-stage" ]
        [ div [ class "example-controls" ]
            [ moveLeftButton
            , moveRightButton
            , moveUpButton
            , moveDownButton
            ]
        , div
            [ class "example-canvas--fluid"
            , style "container-type" "size"
            ]
            [ box ]
        ]
module Animation.Sub.InterruptingAnimations.MultipleAxes.Main exposing (main)

import Anim.Builder exposing (AnimBuilder, ForSub)
import Anim.Engine.Sub as Sub
import Anim.Property.Translate as Translate
import Anim.Unit exposing (Unit(..))
import Browser
import Html exposing (Html, 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
        , update = update
        , view = view
        , subscriptions = subscriptions
        }



-- MODEL


animGroupName : String
animGroupName =
    "movingBox"


type alias Model =
    { animState : Sub.AnimState }


type XPos
    = XLeft
    | XCenter
    | XRight


type YPos
    = YTop
    | YCenter
    | YBottom


boxPct : Float
boxPct =
    12


targetSpeed : Float
targetSpeed =
    25


centerXCqw : Float
centerXCqw =
    (100 - boxPct) / 2


centerYCqh : Float
centerYCqh =
    (100 - boxPct) / 2


init : ( Model, Cmd Msg )
init =
    ( { animState =
            Sub.init
                [ Translate.initXY animGroupName centerXCqw centerYCqh
                    >> Translate.cssUnitX Cqw
                    >> Translate.cssUnitY Cqh
                ]
      }
    , Cmd.none
    )



-- POSITION HELPERS


targetX : XPos -> Float
targetX pos =
    case pos of
        XLeft ->
            0

        XCenter ->
            (100 - boxPct) / 2

        XRight ->
            100 - boxPct


targetY : YPos -> Float
targetY pos =
    case pos of
        YTop ->
            0

        YCenter ->
            (100 - boxPct) / 2

        YBottom ->
            100 - boxPct



-- ANIMATIONS


moveBoxX : Float -> Sub.EngineBuilder -> Sub.EngineBuilder
moveBoxX x =
    moveBox <|
        Translate.toX x


moveBoxY : Float -> Sub.EngineBuilder -> Sub.EngineBuilder
moveBoxY y =
    moveBox <|
        Translate.toY y


moveBox : (Translate.Builder ForSub -> Translate.Builder ForSub) -> Sub.EngineBuilder -> Sub.EngineBuilder
moveBox moveFunc =
    Translate.begin
        >> moveFunc
        >> Translate.speed targetSpeed
        >> Translate.easing QuintOut
        >> Translate.end



-- UPDATE


type Msg
    = GotAnimationUpdate Sub.AnimMsg
    | MoveLeft
    | MoveRight
    | MoveUp
    | MoveDown


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

        MoveLeft ->
            ( { model
                | animState =
                    Sub.animate model.animState <|
                        Sub.for animGroupName
                            >> moveBoxX (targetX XLeft)
              }
            , Cmd.none
            )

        MoveRight ->
            ( { model
                | animState =
                    Sub.animate model.animState <|
                        Sub.for animGroupName
                            >> moveBoxX (targetX XRight)
              }
            , Cmd.none
            )

        MoveUp ->
            ( { model
                | animState =
                    Sub.animate model.animState <|
                        Sub.for animGroupName
                            >> moveBoxY (targetY YTop)
              }
            , Cmd.none
            )

        MoveDown ->
            ( { model
                | animState =
                    Sub.animate model.animState <|
                        Sub.for animGroupName
                            >> moveBoxY (targetY YBottom)
              }
            , Cmd.none
            )



-- SUBSCRIPTIONS


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



-- VIEW


view : Model -> Html Msg
view model =
    let
        button bgColor label onClickMsg =
            Html.button
                [ onClick onClickMsg
                , class "ui-action-button"
                , style "background-color" bgColor
                ]
                [ text label ]

        moveLeftButton =
            button "#007BFF" "Move Left" MoveLeft

        moveRightButton =
            button "#28A745" "Move Right" MoveRight

        moveUpButton =
            button "#6F42C1" "Move Up" MoveUp

        moveDownButton =
            button "#FFC107" "Move Down" MoveDown

        box =
            div
                (Sub.attributes animGroupName model.animState
                    ++ [ style "width" (String.fromFloat boxPct ++ "cqw")
                       , style "height" (String.fromFloat boxPct ++ "cqh")
                       , style "background-color" "#FF5733"
                       , style "border-radius" "8px"
                       , style "position" "absolute"
                       , style "top" "0"
                       , style "left" "0"
                       ]
                )
                []
    in
    div [ class "example-stage" ]
        [ div [ class "example-controls" ]
            [ moveLeftButton
            , moveRightButton
            , moveUpButton
            , moveDownButton
            ]
        , div
            [ class "example-canvas--fluid"
            , style "container-type" "size"
            ]
            [ box ]
        ]
port module Animation.WAAPI.InterruptingAnimations.MultipleAxes.Main exposing (main)

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



-- MAIN


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



-- PORTS


port motionCmd : Encode.Value -> Cmd msg


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



-- MODEL


animGroup : String
animGroup =
    "movingBox"


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


{-| Box size expressed as a percentage of the canvas on each axis.
The box width is `boxPct cqw`, height is `boxPct cqh`, and translate
targets are expressed in the matching axis-unit. Everything scales
with the canvas - no Elm-side resize handling required.
-}
boxPct : Float
boxPct =
    12


centerXCqw : Float
centerXCqw =
    (100 - boxPct) / 2


centerYCqh : Float
centerYCqh =
    (100 - boxPct) / 2



-- INIT


init : ( Model, Cmd Msg )
init =
    ( { animState =
            WAAPI.init motionCmd motionMsg <|
                [ Translate.initXY animGroup centerXCqw centerYCqh >> Translate.cssUnitX Cqw >> Translate.cssUnitY Cqh
                ]
      }
    , Cmd.none
    )



-- POSITION HELPERS


type XPos
    = XLeft
    | XCenter
    | XRight


type YPos
    = YTop
    | YCenter
    | YBottom


{-| X target in `cqw` (0..100 - boxPct).
-}
targetX : XPos -> Float
targetX pos =
    case pos of
        XLeft ->
            0

        XCenter ->
            (100 - boxPct) / 2

        XRight ->
            100 - boxPct


{-| Y target in `cqh` (0..100 - boxPct).
-}
targetY : YPos -> Float
targetY pos =
    case pos of
        YTop ->
            0

        YCenter ->
            (100 - boxPct) / 2

        YBottom ->
            100 - boxPct



-- ANIMATIONS


moveBoxX : Float -> WAAPI.EngineBuilder -> WAAPI.EngineBuilder
moveBoxX x =
    moveBox <|
        Translate.toX x


moveBoxY : Float -> WAAPI.EngineBuilder -> WAAPI.EngineBuilder
moveBoxY y =
    moveBox <|
        Translate.toY y


moveBox : (Translate.Builder ForWAAPI -> Translate.Builder ForWAAPI) -> WAAPI.EngineBuilder -> WAAPI.EngineBuilder
moveBox moveFunc =
    WAAPI.for animGroup
        >> Translate.begin
        >> moveFunc
        >> Translate.speed 25
        >> Translate.easing QuintOut
        >> Translate.end



-- UPDATE


type Msg
    = GotAnimationUpdate WAAPI.AnimMsg
    | MoveLeft
    | MoveRight
    | MoveUp
    | MoveDown


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

        MoveLeft ->
            let
                ( newAnimState, cmd ) =
                    WAAPI.animate model.animState <|
                        moveBoxX (targetX XLeft)
            in
            ( { model | animState = newAnimState }
            , cmd
            )

        MoveRight ->
            let
                ( newAnimState, cmd ) =
                    WAAPI.animate model.animState <|
                        moveBoxX (targetX XRight)
            in
            ( { model | animState = newAnimState }
            , cmd
            )

        MoveUp ->
            let
                ( newAnimState, cmd ) =
                    WAAPI.animate model.animState <|
                        moveBoxY (targetY YTop)
            in
            ( { model | animState = newAnimState }
            , cmd
            )

        MoveDown ->
            let
                ( newAnimState, cmd ) =
                    WAAPI.animate model.animState <|
                        moveBoxY (targetY YBottom)
            in
            ( { model | animState = newAnimState }
            , cmd
            )



-- SUBSCRIPTIONS


subscriptions : Model -> Sub.Sub Msg
subscriptions model =
    WAAPI.subscriptions GotAnimationUpdate model.animState



-- VIEW


view : Model -> Html Msg
view model =
    let
        button bgColor label onClickMsg =
            Html.button
                [ onClick onClickMsg
                , class "ui-action-button"
                , style "background-color" bgColor
                ]
                [ text label ]

        moveLeftButton =
            button "#007BFF" "Move Left" MoveLeft

        moveRightButton =
            button "#28A745" "Move Right" MoveRight

        moveUpButton =
            button "#6F42C1" "Move Up" MoveUp

        moveDownButton =
            button "#FFC107" "Move Down" MoveDown

        box =
            div
                (WAAPI.attributes animGroup model.animState
                    ++ [ style "width" (String.fromFloat boxPct ++ "cqw")
                       , style "height" (String.fromFloat boxPct ++ "cqh")
                       , style "background-color" "#FF5733"
                       , style "border-radius" "8px"
                       , style "position" "absolute"
                       , style "top" "0"
                       , style "left" "0"
                       ]
                )
                []
    in
    div [ class "example-stage" ]
        [ div [ class "example-controls" ]
            [ moveLeftButton
            , moveRightButton
            , moveUpButton
            , moveDownButton
            ]
        , div
            [ class "example-canvas--fluid"
            , style "container-type" "size"
            ]
            [ box ]
        ]

Freezing Axes with freeze*

The example above demonstrates the default behaviour, but this can be changed with the family of freeze* functions.

The freeze* functions let you opt in to freezing specific axes at their current mid-flight values. When a frozen axis is encountered during animation, it holds its current position while only the specified axes animate to new targets.

In the examples below, try the same sequence — click "Move Right" then "Move Up". The box now travels straight up from wherever it is, because freezeX holds the X axis at its current position.

View Examples

Behaviour: Frozen axis holds its current position while the other axis animates to the new target

Behaviour: Frozen axis holds its current position while the other axis animates to the new target

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

import Anim.Builder exposing (AnimBuilder, ForSub)
import Anim.Engine.Sub as Sub
import Anim.Property.Translate as Translate
import Anim.Unit exposing (Unit(..))
import Browser
import Html exposing (Html, 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
        , update = update
        , view = view
        , subscriptions = subscriptions
        }



-- MODEL


animGroupName : String
animGroupName =
    "movingBox"


type alias Model =
    { animState : Sub.AnimState }


type XPos
    = XLeft
    | XCenter
    | XRight


type YPos
    = YTop
    | YCenter
    | YBottom


boxPct : Float
boxPct =
    12


centerXCqw : Float
centerXCqw =
    (100 - boxPct) / 2


centerYCqh : Float
centerYCqh =
    (100 - boxPct) / 2


init : ( Model, Cmd Msg )
init =
    ( { animState =
            Sub.init
                [ Translate.initXY animGroupName centerXCqw centerYCqh >> Translate.cssUnitX Cqw >> Translate.cssUnitY Cqh
                ]
      }
    , Cmd.none
    )



-- POSITION HELPERS


targetX : XPos -> Float
targetX pos =
    case pos of
        XLeft ->
            0

        XCenter ->
            (100 - boxPct) / 2

        XRight ->
            100 - boxPct


targetY : YPos -> Float
targetY pos =
    case pos of
        YTop ->
            0

        YCenter ->
            (100 - boxPct) / 2

        YBottom ->
            100 - boxPct



-- ANIMATIONS


moveBoxX : Float -> Sub.EngineBuilder -> Sub.EngineBuilder
moveBoxX x =
    moveBox <|
        Translate.toX x


moveBoxY : Float -> Sub.EngineBuilder -> Sub.EngineBuilder
moveBoxY y =
    moveBox <|
        Translate.toY y


moveBox : (Translate.Builder ForSub -> Translate.Builder ForSub) -> Sub.EngineBuilder -> Sub.EngineBuilder
moveBox moveFunc =
    Translate.begin
        >> moveFunc
        >> Translate.speed 25
        >> Translate.easing BounceOut
        >> Translate.end



-- UPDATE


type Msg
    = GotAnimationUpdate Sub.AnimMsg
    | MoveLeft
    | MoveRight
    | MoveUp
    | MoveDown


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

        MoveLeft ->
            ( { model
                | animState =
                    Sub.animate model.animState <|
                        Sub.freezeY [ Sub.translate ]
                            >> Sub.for animGroupName
                            >> moveBoxX (targetX XLeft)
              }
            , Cmd.none
            )

        MoveRight ->
            ( { model
                | animState =
                    Sub.animate model.animState <|
                        Sub.freezeY [ Sub.translate ]
                            >> Sub.for animGroupName
                            >> moveBoxX (targetX XRight)
              }
            , Cmd.none
            )

        MoveUp ->
            ( { model
                | animState =
                    Sub.animate model.animState <|
                        Sub.freezeX [ Sub.translate ]
                            >> Sub.for animGroupName
                            >> moveBoxY (targetY YTop)
              }
            , Cmd.none
            )

        MoveDown ->
            ( { model
                | animState =
                    Sub.animate model.animState <|
                        Sub.freezeX [ Sub.translate ]
                            >> Sub.for animGroupName
                            >> moveBoxY (targetY YBottom)
              }
            , Cmd.none
            )



-- SUBSCRIPTIONS


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



-- VIEW


view : Model -> Html Msg
view model =
    let
        button bgColor label onClickMsg =
            Html.button
                [ onClick onClickMsg
                , class "ui-action-button"
                , style "background-color" bgColor
                ]
                [ text label ]

        moveLeftButton =
            button "#007BFF" "Move Left" MoveLeft

        moveRightButton =
            button "#28A745" "Move Right" MoveRight

        moveUpButton =
            button "#6F42C1" "Move Up" MoveUp

        moveDownButton =
            button "#FFC107" "Move Down" MoveDown

        box =
            div
                (Sub.attributes animGroupName model.animState
                    ++ [ style "width" (String.fromFloat boxPct ++ "cqw")
                       , style "height" (String.fromFloat boxPct ++ "cqh")
                       , style "background-color" "#FF5733"
                       , style "border-radius" "8px"
                       , style "position" "absolute"
                       , style "top" "0"
                       , style "left" "0"
                       ]
                )
                []
    in
    div [ class "example-stage" ]
        [ div [ class "example-controls" ]
            [ moveLeftButton
            , moveRightButton
            , moveUpButton
            , moveDownButton
            ]
        , div
            [ class "example-canvas--fluid"
            , style "container-type" "size"
            ]
            [ box ]
        ]
port module Animation.WAAPI.InterruptingAnimations.FreezeAxis.Main exposing (main)

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



-- MAIN


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



-- PORTS


port motionCmd : Encode.Value -> Cmd msg


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



-- MODEL


animGroup : String
animGroup =
    "movingBox"


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


{-| Box size expressed as a percentage of the canvas on each axis.
The box width is `boxPct cqw`, height is `boxPct cqh`, and translate
targets are expressed in the matching axis-unit. Everything scales
with the canvas - no Elm-side resize handling required.
-}
boxPct : Float
boxPct =
    12


centerXCqw : Float
centerXCqw =
    (100 - boxPct) / 2


centerYCqh : Float
centerYCqh =
    (100 - boxPct) / 2



-- INIT


init : ( Model, Cmd Msg )
init =
    ( { animState =
            WAAPI.init motionCmd motionMsg <|
                [ Translate.initXY animGroup centerXCqw centerYCqh >> Translate.cssUnitX Cqw >> Translate.cssUnitY Cqh
                ]
      }
    , Cmd.none
    )



-- POSITION HELPERS


type XPos
    = XLeft
    | XCenter
    | XRight


type YPos
    = YTop
    | YCenter
    | YBottom


{-| X target in `cqw` (0..100 - boxPct).
-}
targetX : XPos -> Float
targetX pos =
    case pos of
        XLeft ->
            0

        XCenter ->
            (100 - boxPct) / 2

        XRight ->
            100 - boxPct


{-| Y target in `cqh` (0..100 - boxPct).
-}
targetY : YPos -> Float
targetY pos =
    case pos of
        YTop ->
            0

        YCenter ->
            (100 - boxPct) / 2

        YBottom ->
            100 - boxPct



-- ANIMATIONS


moveBoxX : Float -> WAAPI.EngineBuilder -> WAAPI.EngineBuilder
moveBoxX x =
    moveBox <|
        Translate.toX x


moveBoxY : Float -> WAAPI.EngineBuilder -> WAAPI.EngineBuilder
moveBoxY y =
    moveBox <|
        Translate.toY y


moveBox : (Translate.Builder ForWAAPI -> Translate.Builder ForWAAPI) -> WAAPI.EngineBuilder -> WAAPI.EngineBuilder
moveBox moveFunc =
    WAAPI.for animGroup
        >> Translate.begin
        >> moveFunc
        >> Translate.speed 25
        >> Translate.easing BounceOut
        >> Translate.end



-- UPDATE


type Msg
    = GotAnimationUpdate WAAPI.AnimMsg
    | MoveLeft
    | MoveRight
    | MoveUp
    | MoveDown


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

        MoveLeft ->
            let
                ( newAnimState, cmd ) =
                    WAAPI.animate model.animState <|
                        WAAPI.freezeY [ WAAPI.translate ]
                            >> moveBoxX (targetX XLeft)
            in
            ( { model | animState = newAnimState }, cmd )

        MoveRight ->
            let
                ( newAnimState, cmd ) =
                    WAAPI.animate model.animState <|
                        WAAPI.freezeY [ WAAPI.translate ]
                            >> moveBoxX (targetX XRight)
            in
            ( { model | animState = newAnimState }, cmd )

        MoveUp ->
            let
                ( newAnimState, cmd ) =
                    WAAPI.animate model.animState <|
                        WAAPI.freezeX [ WAAPI.translate ]
                            >> moveBoxY (targetY YTop)
            in
            ( { model | animState = newAnimState }, cmd )

        MoveDown ->
            let
                ( newAnimState, cmd ) =
                    WAAPI.animate model.animState <|
                        WAAPI.freezeX [ WAAPI.translate ]
                            >> moveBoxY (targetY YBottom)
            in
            ( { model | animState = newAnimState }, cmd )



-- SUBSCRIPTIONS


subscriptions : Model -> Sub.Sub Msg
subscriptions model =
    WAAPI.subscriptions GotAnimationUpdate model.animState



-- VIEW


view : Model -> Html Msg
view model =
    let
        button bgColor label onClickMsg =
            Html.button
                [ onClick onClickMsg
                , class "ui-action-button"
                , style "background-color" bgColor
                ]
                [ text label ]

        moveLeftButton =
            button "#007BFF" "Move Left" MoveLeft

        moveRightButton =
            button "#28A745" "Move Right" MoveRight

        moveUpButton =
            button "#6F42C1" "Move Up" MoveUp

        moveDownButton =
            button "#FFC107" "Move Down" MoveDown

        box =
            div
                (WAAPI.attributes animGroup model.animState
                    ++ [ style "width" (String.fromFloat boxPct ++ "cqw")
                       , style "height" (String.fromFloat boxPct ++ "cqh")
                       , style "background-color" "#FF5733"
                       , style "border-radius" "8px"
                       , style "position" "absolute"
                       , style "top" "0"
                       , style "left" "0"
                       ]
                )
                []
    in
    div [ class "example-stage" ]
        [ div [ class "example-controls" ]
            [ moveLeftButton
            , moveRightButton
            , moveUpButton
            , moveDownButton
            ]
        , div
            [ class "example-canvas--fluid"
            , style "container-type" "size"
            ]
            [ box ]
        ]

freeze* is available on the Sub and WAAPI engines.

Next Steps

Now that you understand how animations handle interruptions, learn how to control them.

Controlling Animations →