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().
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.