Skip to content

Animation Examples

Interactive examples demonstrating Elm Motion capabilities.

Each example uses the exact same animation for each of the Engines - quickly compare the behaviour of each Engine for different animation types.

Hello Text

Fades in text when the page loads. The obligatory "Hello" example.

View Example

Go to page


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

Go to page


Fade In/Out

Fade an element in and out with buttons.

View Examples

Go to page


Button Hovers

Three different hover effects.

View Examples

Note how animating Size causes browser reflow and repaint; as the button grows and shrinks, it affects the layout of surrounding elements. In contrast, Scale and Translate have no effect on the surrounding elements. More on this later.

Go to page


Interrupting Animations

Trigger an animation, then interrupt it mid-flight.

Single Property

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

Multiple Properties

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.

Multiple Axes

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

Freeze Axis

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

Go to page


Controlling Animations

Control the ball animation with the buttons.

View Examples

Go to page


Discrete Properties

Show and hide elements with smooth fades using discrete CSS properties like display.

View Examples

Go to page


3D Animations

Rotating cube with expanding sides.

View Examples

Go to page


Transform Order

There are 6 boxes in the center, each one is triggered with the same animation, the only difference is the transform order.

View Examples

Go to page


Custom Properties

Border Radius

Animate border-radius using the generic Anim.Property.Custom module.

View Example

Go to page

Border Color

Animate border-color using the generic Anim.Property.CustomColor module.

View Example

Go to page

Timeline Engines

Scroll-driven animation examples using the dedicated timeline engines.

ScrollTimeline Engine

Scroll the page, and the progress bar will animate in response.

View Example

View Source Code
port module Animation.ScrollTimeline.Main exposing (main)

import Anim.Builder exposing (AnimBuilder)
import Anim.Engine.ScrollTimeline as ScrollTimeline exposing (Container(..))
import Anim.Extra.Color as Color exposing (Color)
import Anim.Property.CustomColor as CustomColor exposing (ColorProperty(..))
import Anim.Property.Scale as Scale
import Browser
import Html exposing (Html, div, h2, p, span, text)
import Html.Attributes exposing (class, id, style)
import Json.Encode as Encode
import Motion.Easing as Easing exposing (Easing(..))



-- PORTS


port motionCmd : Encode.Value -> Cmd msg



-- MAIN


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



-- ANIMATION


progressBarAnim : String
progressBarAnim =
    "scrollProgress"





scrollProgress : ScrollTimeline.EngineBuilder -> ScrollTimeline.EngineBuilder
scrollProgress =
    ScrollTimeline.for progressBarAnim
        >> Scale.begin
        >> Scale.fromX 0
        >> Scale.toX 1
        >> Scale.end
        >> CustomColor.begin BackgroundColor
        >> CustomColor.from Color.red
        >> CustomColor.to Color.green
        >> CustomColor.end



-- INIT


init : ( (), Cmd msg )
init =
    ( ()
    , ScrollTimeline.animate motionCmd Document scrollProgress
    )



-- VIEW


view : () -> Html msg
view _ =
    div
        [ style "font-family" "system-ui, sans-serif"
        , style "color" "#1f2937"
        , style "background" "#ffffff"
        ]
        [ -- Fixed progress bar at top of page
          div
            [ style "position" "fixed"
            , style "top" "0"
            , style "left" "0"
            , style "width" "100%"
            , style "height" "5px"
            , style "background" "#e5e7eb"
            , style "z-index" "100"
            ]
            [ div
                (ScrollTimeline.attributes progressBarAnim
                    ++ [ style "width" "100%"
                       , style "height" "100%"
                       , style "transform-origin" "left center"
                       , style "transform" "scaleX(0)"
                       ]
                )
                []
            ]
        , div
            [ style "text-align" "center"
            , style "padding" "clamp(48px, 10vh, 80px) clamp(20px, 5vw, 40px) clamp(28px, 6vh, 40px)"
            , style "background" "linear-gradient(135deg, #ede9fe, #ddd6fe)"
            ]
            [ h2
                [ style "font-size" "clamp(1.6rem, 4vw + 0.8rem, 2.5rem)"
                , style "font-weight" "700"
                , style "margin" "0 0 16px"
                , style "color" "#4c1d95"
                ]
                [ text "Scroll Timeline" ]
            , p
                [ style "font-size" "clamp(0.95rem, 1vw + 0.6rem, 1.1rem)"
                , style "color" "#6d28d9"
                , style "margin" "0"
                ]
                [ text "Scroll down and watch the bar fill as the page moves from top to bottom." ]
            ]

        -- Scrollable content cards
        , div
            [ style "max-width" "700px"
            , style "margin" "0 auto"
            , style "padding" "clamp(36px, 8vh, 60px) clamp(16px, 5vw, 40px)"
            , style "display" "flex"
            , style "flex-direction" "column"
            , style "gap" "clamp(28px, 6vh, 60px)"
            ]
            (List.map contentCard cards)
        ]


type alias CardData =
    { label : String
    , color : String
    , title : String
    , body : String
    }


cards : List CardData
cards =
    [ { label = "01"
      , color = "#6366f1"
      , title = "Top to bottom"
      , body = "The timeline starts at 0% at the top of the page and reaches 100% at the bottom."
      }
    , { label = "02"
      , color = "#8b5cf6"
      , title = "One scroll, two effects"
      , body = "The same timeline drives both size and color, so one scroll gesture controls the whole bar."
      }
    , { label = "03"
      , color = "#a78bfa"
      , title = "Read progress at a glance"
      , body = "Short red bar means early in the page, long green bar means you are near the end."
      }
    , { label = "04"
      , color = "#7c3aed"
      , title = "Simple trigger"
      , body = "Call ScrollTimeline.animate once in init, then the browser keeps everything in sync while you scroll."
      }
    , { label = "05"
      , color = "#5b21b6"
      , title = "Easy to reuse"
      , body = "Attach ScrollTimeline.attributes to any element and map scroll progress to the properties you want."
      }
    ]


contentCard : CardData -> Html msg
contentCard card =
    div
        [ style "display" "flex"
        , style "gap" "clamp(14px, 3vw, 24px)"
        , style "align-items" "flex-start"
        , style "padding" "clamp(20px, 4vw, 32px)"
        , style "background" "white"
        , style "border-radius" "16px"
        , style "box-shadow" "0 4px 24px rgba(99,102,241,0.08)"
        ]
        [ span
            [ style "font-size" "clamp(1.4rem, 3vw + 0.4rem, 2rem)"
            , style "font-weight" "800"
            , style "color" card.color
            , style "flex-shrink" "0"
            , style "line-height" "1"
            , style "padding-top" "4px"
            ]
            [ text card.label ]
        , div []
            [ h2
                [ style "font-size" "clamp(1.05rem, 1.5vw + 0.5rem, 1.3rem)"
                , style "font-weight" "700"
                , style "margin" "0 0 10px"
                , style "color" "#111827"
                ]
                [ text card.title ]
            , p
                [ style "font-size" "clamp(0.9rem, 1vw + 0.5rem, 1rem)"
                , style "line-height" "1.7"
                , style "color" "#6b7280"
                , style "margin" "0"
                ]
                [ text card.body ]
            ]
        ]

ViewTimeline Engine

Scroll the page, and the different sections will fade in and slide up as they are scrolled into view.

View Example

View Source Code
port module Animation.ViewTimeline.Main exposing (main)

import Anim.Builder exposing (AnimBuilder)
import Anim.Engine.ViewTimeline as ViewTimeline exposing (Range(..), Unit(..))
import Anim.Property.Opacity as Opacity
import Anim.Property.Translate as Translate
import Browser
import Html exposing (Html, div, h2, p, span, text)
import Html.Attributes exposing (class, id, style)
import Json.Encode as Encode
import Motion.Easing as Easing exposing (Easing(..))



-- PORTS


port motionCmd : Encode.Value -> Cmd msg



-- MAIN


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



-- ANIMATION


revealCard : String -> ViewTimeline.EngineBuilder -> ViewTimeline.EngineBuilder
revealCard animGroupName =
    ViewTimeline.for animGroupName
        >> Opacity.begin
        >> Opacity.from 0
        >> Opacity.to 1
        >> Opacity.end
        >> Translate.begin
        >> Translate.fromY 100
        >> Translate.toY 0
        >> Translate.end



-- INIT


init : ( (), Cmd msg )
init =
    ( ()
    , cards
        |> List.map
            (\card ->
                ViewTimeline.animate motionCmd <|
                    ViewTimeline.rangeStart (Entry 10 Perc)
                        >> ViewTimeline.rangeEnd (Cover 30 Perc)
                        >> ViewTimeline.easing BounceInOut
                        >> revealCard card.animGroupName
            )
        |> Cmd.batch
    )



-- VIEW


view : () -> Html msg
view _ =
    div
        [ style "font-family" "system-ui, sans-serif"
        , style "color" "#1f2937"
        , style "background" "#f9fafb"
        ]
        [ -- Page header
          div
            [ style "text-align" "center"
            , style "padding" "80px 40px 60px"
            , style "background" "linear-gradient(135deg, #ede9fe, #ddd6fe)"
            ]
            [ h2
                [ style "font-size" "2.5rem"
                , style "font-weight" "700"
                , style "margin" "0 0 16px"
                , style "color" "#4c1d95"
                ]
                [ text "View Timeline" ]
            , p
                [ style "font-size" "1.1rem"
                , style "color" "#6d28d9"
                , style "margin" "0"
                ]
                [ text "Scroll down - each card animates as it enters the viewport." ]
            ]

        -- Cards
        , div
            [ style "max-width" "700px"
            , style "margin" "0 auto"
            , style "padding" "clamp(24px, 6vmin, 60px) clamp(16px, 4.5vmin, 40px)"
            , style "display" "flex"
            , style "flex-direction" "column"
            , style "gap" "clamp(28px, 6vmin, 60px)"
            ]
            (List.map cardView cards)
        ]


type alias CardData =
    { animGroupName : String
    , color : String
    , label : String
    , title : String
    , body : String
    }


cards : List CardData
cards =
    [ { animGroupName = "view-card-1"
      , color = "#6366f1"
      , label = "01"
      , title = "Enter from below"
      , body = "Each card uses a ViewTimeline tied to itself as the scroll subject. As the card enters the viewport, it fades in and slides upward."
      }
    , { animGroupName = "view-card-2"
      , color = "#8b5cf6"
      , label = "02"
      , title = "Independent timelines"
      , body = "Every card has its own ViewTimeline. Animations are fully independent - each one triggers only when that specific card enters the viewport."
      }
    , { animGroupName = "view-card-3"
      , color = "#a78bfa"
      , label = "03"
      , title = "Range control"
      , body = "The rangeStart and rangeEnd functions define exactly when during the card's lifecycle the animation plays - entry, cover, contain, or exit."
      }
    , { animGroupName = "view-card-4"
      , color = "#7c3aed"
      , label = "04"
      , title = "Fire and forget"
      , body = "View timeline animations are fire-and-forget. No AnimState required - just trigger in your init and the browser handles the rest."
      }
    , { animGroupName = "view-card-5"
      , color = "#5b21b6"
      , label = "05"
      , title = "Composable builders"
      , body = "Combine opacity and translate in a single pipeline to create polished reveal effects with minimal code."
      }
    , { animGroupName = "view-card-6"
      , color = "#4c1d95"
      , label = "06"
      , title = "WAAPI powered"
      , body = "All animations run via the Web Animations API on the compositor thread - no JavaScript timers, no Elm subscriptions, maximum performance."
      }
    ]


cardView : CardData -> Html msg
cardView card =
    div
        (ViewTimeline.attributes card.animGroupName
            ++ [ style "display" "flex"
               , style "flex-direction" "column"
               , style "gap" "24px"
               , style "align-items" "flex-start"
               , style "padding" "clamp(18px, 4vmin, 32px)"
               , style "background" "white"
               , style "border-radius" "16px"
               , style "box-shadow" "0 4px 24px rgba(99,102,241,0.08)"
               , style "opacity" "0"
               ]
        )
        [ div
            [ style "display" "flex"
            , style "flex-direction" "column"
            , style "gap" "10px"
            , style "align-items" "flex-start"
            ]
            [ span
                [ style "font-size" "2rem"
                , style "font-weight" "800"
                , style "color" card.color
                , style "flex-shrink" "0"
                , style "line-height" "1"
                , style "padding-top" "4px"
                ]
                [ text card.label ]
            , div
                [ style "flex" "1"
                , style "min-width" "0"
                , style "text-align" "left"
                ]
                [ h2
                    [ style "font-size" "1.3rem"
                    , style "font-weight" "700"
                    , style "margin" "0"
                    , style "color" "#111827"
                    , style "overflow-wrap" "break-word"
                    , style "word-break" "break-word"
                    ]
                    [ text card.title ]
                ]
            ]
        , p
            [ style "font-size" "1rem"
            , style "line-height" "1.7"
            , style "color" "#6b7280"
            , style "margin" "0"
            ]
            [ text card.body ]
        ]