Skip to content

Easing

Easing controls how property values accelerate and decelerate during animation.

Standard Easings

These are the standard easing curves calculated using the functions from elm-community/easing-functions.

Linear

Constant speed throughout. Useful when you want neutral movement with no acceleration or deceleration.

Ease

The standard CSS easing functions.

Easing Feel
EaseIn Starts slow, ends fast
EaseOut Starts fast, ends slow
EaseInOut Slow at both ends

Sine

Gentle, subtle easing based on sine curve.

Easing Feel
SineIn Gentle acceleration
SineOut Gentle deceleration
SineInOut Gentle both ends

Quad

Quadratic (power of 2) — slightly more pronounced than sine.

Easing Feel
QuadIn Moderate acceleration
QuadOut Moderate deceleration
QuadInOut Moderate both ends

Cubic

Cubic (power of 3) — more noticeable acceleration/deceleration.

Easing Feel
CubicIn Noticeable acceleration
CubicOut Noticeable deceleration
CubicInOut Noticeable both ends

Quart

Quartic (power of 4) — dramatic effect.

Easing Feel
QuartIn Strong acceleration
QuartOut Strong deceleration
QuartInOut Strong both ends

Quint

Quintic (power of 5) — very dramatic.

Easing Feel
QuintIn Very strong acceleration
QuintOut Very strong deceleration
QuintInOut Very strong both ends

Expo

Exponential — extremely dramatic.

Easing Feel
ExpoIn Explosive acceleration
ExpoOut Explosive deceleration
ExpoInOut Explosive both ends

Circ

Circular — based on quarter circle.

Easing Feel
CircIn Circular acceleration
CircOut Circular deceleration
CircInOut Circular both ends

Back

Overshoots slightly then returns.

Easing Feel
BackIn Pulls back then accelerates
BackOut Overshoots then settles
BackInOut Both effects

Elastic

Spring-like bounce effect.

Easing Feel
ElasticIn Winds up like a spring
ElasticOut Springs and oscillates
ElasticInOut Both effects

Bounce

Bouncing ball effect.

Easing Feel
BounceIn Bounces at start
BounceOut Bounces at end
BounceInOut Both effects

CubicBezier

Custom easing curve defined by two control points — the same format used by CSS cubic-bezier().

View Source Code
CubicBezier 0.68 -0.55 0.265 1.55

The four parameters (x1 y1 x2 y2) define the curve's control points. Use tools like cubic-bezier.com to visualize and create custom curves.

Choosing an Easing

Use Case Recommended Easing Why
Entering elements QuintOut / CubicOut Arrives fast, settles smoothly
Exiting elements QuintIn / CubicIn Leaves with acceleration
State-to-state transitions QuintInOut / CubicInOut Balanced easing at both ends
Playful motion moments BackOut / ElasticOut / BounceOut Adds character and energy

For entrances

Use Out variants — elements should arrive and settle smoothly.

For exits

Use In variants — elements should accelerate away.

For state changes

Use InOut variants — smooth transitions between states.

Examples

The same horizontal translate, driven by six different easing curves and rendered in each of the four time-driven engines. Click a curve to play the animation; each click also flips the direction so you can keep tapping to compare. Same Translate.duration 1500 for every curve.

View Example

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

import Anim.Builder exposing (AnimBuilder)
import Anim.Engine.Transition as Transition
import Anim.Property.Translate as Translate
import Browser
import Html exposing (Html, button, div, text)
import Html.Attributes exposing (class, style)
import Html.Events exposing (onClick)
import Motion.Easing as Easing exposing (Easing(..))



-- MAIN


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



-- MODEL


type alias Model =
    { animState : Transition.AnimState
    , easing : Easing
    , atEnd : Bool
    }


animGroup : String
animGroup =
    "easingBox"


init : ( Model, Cmd Msg )
init =
    ( { animState =
            Transition.init
                [ Translate.initX animGroup 0 ]
      , easing = CubicOut
      , atEnd = False
      }
    , Cmd.none
    )



-- ANIMATION


animateTo : Float -> Easing -> AnimBuilder { eng | withTiming : () } -> AnimBuilder { eng | withTiming : () }
animateTo x easing =
    Translate.begin
        >> Translate.toX x
        >> Translate.speed 350
        >> Translate.easing easing
        >> Translate.end



-- UPDATE


type Msg
    = Play Easing


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        Play easing ->
            let
                target =
                    if model.atEnd then
                        0

                    else
                        420
            in
            ( { model
                | animState =
                    Transition.animate model.animState <|
                        Transition.for animGroup
                            >> animateTo target easing
                , easing = easing
                , atEnd = not model.atEnd
              }
            , Cmd.none
            )



-- VIEW


curves : List ( Easing, String )
curves =
    [ ( Linear, "Linear" )
    , ( QuadOut, "QuadOut" )
    , ( ExpoOut, "ExpoOut" )
    , ( ElasticOut, "ElasticOut" )
    , ( BounceOut, "BounceOut" )
    , ( BackInOut, "BackInOut" )
    ]


view : Model -> Html Msg
view model =
    div
        [ class "example-stage"
        , style "height" "auto"
        , style "min-height" "260px"
        ]
        [ div [ class "example-controls" ]
            (List.map (curveButton model.easing) curves)
        , div
            [ style "width" "100%"
            , style "max-width" "480px"
            , style "height" "70px"
            , style "display" "flex"
            , style "align-items" "center"
            ]
            [ div
                (Transition.attributes animGroup model.animState
                    ++ [ style "width" "60px"
                       , style "height" "60px"
                       , style "background-color" "#3498db"
                       , style "border-radius" "8px"
                       ]
                )
                []
            ]
        ]


curveButton : Easing -> ( Easing, String ) -> Html Msg
curveButton selected ( easing, label ) =
    let
        variant =
            if easing == selected then
                "primary"

            else
                "purple"
    in
    button
        [ onClick (Play easing)
        , class ("ui-action-button " ++ variant)
        ]
        [ text label ]
module Animation.Keyframe.Easings.Main exposing (main)

import Anim.Builder exposing (AnimBuilder)
import Anim.Engine.Keyframe as Keyframe
import Anim.Property.Translate as Translate
import Browser
import Html exposing (Html, button, div, text)
import Html.Attributes exposing (class, style)
import Html.Events exposing (onClick)
import Motion.Easing as Easing exposing (Easing(..))



-- MAIN


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



-- MODEL


type alias Model =
    { animState : Keyframe.AnimState
    , easing : Easing
    , atEnd : Bool
    }


animGroup : String
animGroup =
    "easingBox"


init : ( Model, Cmd Msg )
init =
    ( { animState =
            Keyframe.init
                [ Translate.initX animGroup 0 ]
      , easing = CubicOut
      , atEnd = False
      }
    , Cmd.none
    )



-- ANIMATION


animateTo : Float -> Easing -> AnimBuilder { eng | withTiming : () } -> AnimBuilder { eng | withTiming : () }
animateTo x easing =
    Translate.begin
        >> Translate.toX x
        >> Translate.speed 350
        >> Translate.easing easing
        >> Translate.end



-- UPDATE


type Msg
    = Play Easing


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        Play easing ->
            let
                target =
                    if model.atEnd then
                        0

                    else
                        420
            in
            ( { model
                | animState =
                    Keyframe.animate model.animState <|
                        Keyframe.for animGroup
                            >> animateTo target easing
                , easing = easing
                , atEnd = not model.atEnd
              }
            , Cmd.none
            )



-- VIEW


curves : List ( Easing, String )
curves =
    [ ( Linear, "Linear" )
    , ( QuadOut, "QuadOut" )
    , ( ExpoOut, "ExpoOut" )
    , ( ElasticOut, "ElasticOut" )
    , ( BounceOut, "BounceOut" )
    , ( BackInOut, "BackInOut" )
    ]


view : Model -> Html Msg
view model =
    div
        [ class "example-stage"
        , style "height" "auto"
        , style "min-height" "260px"
        ]
        [ Keyframe.styleNode model.animState
        , div [ class "example-controls" ]
            (List.map (curveButton model.easing) curves)
        , div
            [ style "width" "100%"
            , style "max-width" "480px"
            , style "height" "70px"
            , style "display" "flex"
            , style "align-items" "center"
            ]
            [ div
                (Keyframe.attributes animGroup model.animState
                    ++ [ style "width" "60px"
                       , style "height" "60px"
                       , style "background-color" "#3498db"
                       , style "border-radius" "8px"
                       ]
                )
                []
            ]
        ]


curveButton : Easing -> ( Easing, String ) -> Html Msg
curveButton selected ( easing, label ) =
    let
        variant =
            if easing == selected then
                "primary"

            else
                "purple"
    in
    button
        [ onClick (Play easing)
        , class ("ui-action-button " ++ variant)
        ]
        [ text label ]
module Animation.Sub.Easings.Main exposing (main)

import Anim.Builder exposing (AnimBuilder)
import Anim.Engine.Sub as Sub
import Anim.Property.Translate as Translate
import Browser
import Html exposing (Html, button, div, text)
import Html.Attributes exposing (class, style)
import Html.Events exposing (onClick)
import Motion.Easing as Easing exposing (Easing(..))



-- MAIN


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



-- MODEL


type alias Model =
    { animState : Sub.AnimState
    , easing : Easing
    , atEnd : Bool
    }


animGroup : String
animGroup =
    "easingBox"


init : ( Model, Cmd Msg )
init =
    ( { animState =
            Sub.init
                [ Translate.initX animGroup 0 ]
      , easing = CubicOut
      , atEnd = False
      }
    , Cmd.none
    )



-- ANIMATION


animateTo : Float -> Easing -> AnimBuilder { eng | withTiming : () } -> AnimBuilder { eng | withTiming : () }
animateTo x easing =
    Translate.begin
        >> Translate.toX x
        >> Translate.speed 350
        >> Translate.easing easing
        >> Translate.end



-- UPDATE


type Msg
    = GotSubMsg Sub.AnimMsg
    | Play Easing


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

        Play easing ->
            let
                target =
                    if model.atEnd then
                        0

                    else
                        420
            in
            ( { model
                | animState =
                    Sub.animate model.animState <|
                        Sub.for animGroup
                            >> animateTo target easing
                , easing = easing
                , atEnd = not model.atEnd
              }
            , Cmd.none
            )



-- SUBSCRIPTIONS


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



-- VIEW


curves : List ( Easing, String )
curves =
    [ ( Linear, "Linear" )
    , ( QuadOut, "QuadOut" )
    , ( ExpoOut, "ExpoOut" )
    , ( ElasticOut, "ElasticOut" )
    , ( BounceOut, "BounceOut" )
    , ( BackInOut, "BackInOut" )
    ]


view : Model -> Html Msg
view model =
    div
        [ class "example-stage"
        , style "height" "auto"
        , style "min-height" "260px"
        ]
        [ div [ class "example-controls" ]
            (List.map (curveButton model.easing) curves)
        , div
            [ style "width" "100%"
            , style "max-width" "480px"
            , style "height" "70px"
            , style "display" "flex"
            , style "align-items" "center"
            ]
            [ div
                (Sub.attributes animGroup model.animState
                    ++ [ style "width" "60px"
                       , style "height" "60px"
                       , style "background-color" "#3498db"
                       , style "border-radius" "8px"
                       ]
                )
                []
            ]
        ]


curveButton : Easing -> ( Easing, String ) -> Html Msg
curveButton selected ( easing, label ) =
    let
        variant =
            if easing == selected then
                "primary"

            else
                "purple"
    in
    button
        [ onClick (Play easing)
        , class ("ui-action-button " ++ variant)
        ]
        [ text label ]
port module Animation.WAAPI.Easings.Main exposing (main)

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



-- MODEL


type alias Model =
    { animState : WAAPI.AnimState Msg
    , easing : Easing
    , atEnd : Bool
    }


animGroup : String
animGroup =
    "easingBox"


init : ( Model, Cmd Msg )
init =
    ( { animState =
            WAAPI.init motionCmd motionMsg <|
                [ Translate.initX animGroup 0 ]
      , easing = CubicOut
      , atEnd = False
      }
    , Cmd.none
    )



-- ANIMATION


animateTo : Float -> Easing -> AnimBuilder { eng | withTiming : () } -> AnimBuilder { eng | withTiming : () }
animateTo x easing =
    Translate.begin
        >> Translate.toX x
        >> Translate.speed 350
        >> Translate.easing easing
        >> Translate.end



-- UPDATE


type Msg
    = GotWaapiMsg WAAPI.AnimMsg
    | Play Easing


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

        Play easing ->
            let
                target =
                    if model.atEnd then
                        0

                    else
                        420

                ( newAnimState, cmd ) =
                    WAAPI.animate model.animState <|
                        WAAPI.for animGroup
                            >> animateTo target easing
            in
            ( { model
                | animState = newAnimState
                , easing = easing
                , atEnd = not model.atEnd
              }
            , cmd
            )



-- SUBSCRIPTIONS


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



-- VIEW


curves : List ( Easing, String )
curves =
    [ ( Linear, "Linear" )
    , ( QuadOut, "QuadOut" )
    , ( ExpoOut, "ExpoOut" )
    , ( ElasticOut, "ElasticOut" )
    , ( BounceOut, "BounceOut" )
    , ( BackInOut, "BackInOut" )
    ]


view : Model -> Html Msg
view model =
    div
        [ class "example-stage"
        , style "height" "auto"
        , style "min-height" "260px"
        ]
        [ div [ class "example-controls" ]
            (List.map (curveButton model.easing) curves)
        , div
            [ style "width" "100%"
            , style "max-width" "480px"
            , style "height" "70px"
            , style "display" "flex"
            , style "align-items" "center"
            ]
            [ div
                (WAAPI.attributes animGroup model.animState
                    ++ [ style "width" "60px"
                       , style "height" "60px"
                       , style "background-color" "#3498db"
                       , style "border-radius" "8px"
                       ]
                )
                []
            ]
        ]


curveButton : Easing -> ( Easing, String ) -> Html Msg
curveButton selected ( easing, label ) =
    let
        variant =
            if easing == selected then
                "primary"

            else
                "purple"
    in
    button
        [ onClick (Play easing)
        , class ("ui-action-button " ++ variant)
        ]
        [ text label ]

Next Steps

For physics-based motion — where the spring's stiffness, damping, and mass decide how long the motion takes — see Spring.

Spring →