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
After three plays the animation ends and the element rests at the final value.
Loop Forever¶
Repeat the animation indefinitely.
View Source Code
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
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.