Skip to content

Playback

Control how many times an animation plays and whether each repeat reverses direction. Every Document timeline engine except Transition supports the same three playback functions.

Function Effect Default
iterations Play the animation a fixed number of times 1
loopForever Repeat the animation indefinitely off
alternate Reverse direction on each repeat (ping-pong) off

Example - Pulsing Dot

A dot in the middle of the screen that pulses by scaling and fading in and out - looping forever and alternating direction on each iteration.

View Example

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

import Anim.Builder exposing (AnimBuilder)
import Anim.Engine.Keyframe as Keyframe
import Anim.Property.Opacity as Opacity
import Anim.Property.Scale as Scale
import Browser
import Html exposing (Html, div)
import Html.Attributes exposing (class, style)
import Motion.Easing exposing (Easing(..))



-- MAIN


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



-- MODEL


type alias Model =
    { animState : Keyframe.AnimState }





init : ( Model, Cmd msg )
init =
    let
        animState =
            Keyframe.init
                [ Scale.init groupName 1
                , Opacity.init groupName 1
                ]
    in
    ( { animState =
            Keyframe.animate animState <|
                Keyframe.for groupName
                    >> pulse
      }
    , Cmd.none
    )



-- ANIMATION
-- Avoid typos from hardcoding strings in multiple places


groupName : String
groupName =
    "pulsingDot"


pulse : Keyframe.EngineBuilder -> Keyframe.EngineBuilder
pulse =
    Keyframe.loopForever
        >> Keyframe.alternate
        >> Keyframe.duration 1000
        >> Keyframe.easing EaseInOut
        >> Scale.begin
        >> Scale.to 0.4
        >> Scale.end
        >> Opacity.begin
        >> Opacity.to 0.3
        >> Opacity.end



-- VIEW


view : Model -> Html msg
view model =
    div
        [ class "example-stage" ]
        [ Keyframe.styleNode model.animState
        , div
            (Keyframe.attributes groupName model.animState
                ++ [ style "width" "80px"
                   , style "height" "80px"
                   , style "border-radius" "50%"
                   , style "background-color" "#e53935"
                   ]
            )
            []
        ]
module Animation.Sub.PulsingDot.Main exposing (main)

import Anim.Builder exposing (AnimBuilder)
import Anim.Engine.Sub as Sub
import Anim.Property.Opacity as Opacity
import Anim.Property.Scale as Scale
import Browser
import Html exposing (Html, div)
import Html.Attributes exposing (class, style)
import Motion.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 }





init : ( Model, Cmd Msg )
init =
    let
        animState =
            Sub.init
                [ Scale.init groupName 1
                , Opacity.init groupName 1
                ]
    in
    ( { animState =
            Sub.animate animState <|
                Sub.for groupName
                    >> pulse
      }
    , Cmd.none
    )



-- ANIMATION
-- Avoid typos from hardcoding strings in multiple places


groupName : String
groupName =
    "pulsingDot"


pulse : Sub.EngineBuilder -> Sub.EngineBuilder
pulse =
    Sub.loopForever
        >> Sub.alternate
        >> Sub.duration 1000
        >> Sub.easing EaseInOut
        >> Scale.begin
        >> Scale.to 0.4
        >> Scale.end
        >> Opacity.begin
        >> Opacity.to 0.3
        >> Opacity.end



-- UPDATE


type Msg
    = GotSubMsg Sub.AnimMsg


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        GotSubMsg animMsg ->
            let
                ( animState, _ ) =
                    Sub.update animMsg model.animState
            in
            ( { model | animState = 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
            (Sub.attributes groupName model.animState
                ++ [ style "width" "80px"
                   , style "height" "80px"
                   , style "border-radius" "50%"
                   , style "background-color" "#e53935"
                   ]
            )
            []
        ]
port module Animation.WAAPI.PulsingDot.Main exposing (main)

import Anim.Builder exposing (AnimBuilder)
import Anim.Engine.WAAPI as WAAPI
import Anim.Property.Opacity as Opacity
import Anim.Property.Scale as Scale
import Browser
import Html exposing (Html, div)
import Html.Attributes exposing (class, style)
import Json.Encode as Encode
import Motion.Easing exposing (Easing(..))



-- PORTS
-- Outgoing Port


port motionCmd : Encode.Value -> Cmd msg



-- Incoming Port


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



-- MAIN


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



-- MODEL


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





init : ( Model msg, Cmd msg )
init =
    let
        animState =
            WAAPI.init motionCmd motionMsg <|
                [ Scale.init groupName 1
                , Opacity.init groupName 1
                ]

        ( newAnimState, cmd ) =
            WAAPI.animate animState <|
                WAAPI.for groupName
                    >> pulse
    in
    ( { animState = newAnimState }
    , cmd
    )



-- ANIMATION
-- Avoid typos from hardcoding strings in multiple places


groupName : String
groupName =
    "pulsingDot"


pulse : WAAPI.EngineBuilder -> WAAPI.EngineBuilder
pulse =
    WAAPI.loopForever
        >> WAAPI.alternate
        >> WAAPI.duration 1000
        >> WAAPI.easing EaseInOut
        >> Scale.begin
        >> Scale.to 0.4
        >> Scale.end
        >> Opacity.begin
        >> Opacity.to 0.3
        >> Opacity.end



-- VIEW


view : Model msg -> Html msg
view model =
    div
        [ class "example-stage" ]
        [ div
            (WAAPI.attributes groupName model.animState
                ++ [ style "width" "80px"
                   , style "height" "80px"
                   , style "border-radius" "50%"
                   , style "background-color" "#e53935"
                   ]
            )
            []
        ]

Iterations

Play the animation n times in the same direction. The element returns to its start value at the beginning of every iteration.

View Source Code
Keyframe.iterations 3
    >> Keyframe.for "blink"
    >> Opacity.begin
    >> Opacity.to 0
    >> Opacity.duration 250
    >> Opacity.end

After three plays the animation ends and the element rests at the final value.

Loop Forever

Repeat the animation indefinitely.

View Source Code
Sub.loopForever
    >> Sub.for "spinner"
    >> Rotate.begin
    >> Rotate.toZ 360
    >> Rotate.duration 1000
    >> Rotate.end

Alternate

Reverse direction on every iteration boundary. The classic ping-pong:

  • iteration 1 plays start -> end
  • iteration 2 plays end -> start
  • iteration 3 plays start -> end
View Source Code
WAAPI.loopForever
    >> WAAPI.alternate
    >> WAAPI.for "bounce"
    >> Translate.begin
    >> Translate.toY -40
    >> Translate.duration 500
    >> Translate.easing EaseInOut
    >> Translate.end

Alternate implies at least two iterations

alternate only has a visible effect when the animation runs more than once. Calling alternate when iterations is unset or 1 automatically bumps iterations to 2. An explicit iterations count (or loopForever) set before or after alternate is preserved.

Next Steps

Shape how each iteration feels with easing curves.

Easing →