gampleman / elm-visualization / Transition

Transition is a module for writing animations. It does not attempt to be an animation library for every use case, it is specifically designed for the needs of data visualization apps. The main idea is that one animates data in some intermediate form and then leaves the view function to display the data as normal.

Setting up animation in an app

While there are many ways to use this module, a typical setup will look like this:

import Browser.Events
import Interpolation exposing (Interpolator)
import Transition exposing (Transition)

type alias Model =
    { transition : Transition MyThing
    }

type Msg
    = Tick Int
    | StartAnimation MyThing

First setup a default transition that doesn't actually do anything:

init : () -> ( Model, Cmd Msg )
init () =
    ( { transition = Transition.constant initialThing }
    , Cmd.none
    )

Next setup a subscription:

subscriptions : Model -> Sub Msg
subscriptions model =
    if Transition.isComplete model.transition then
        Sub.none

    else
        Browser.Events.onAnimationFrameDelta (round >> Tick)

Define an interpolator for your value:

interpolateThing : MyThing -> MyThing -> Interpolator MyThing
interpolateThing from to =
     -- ...

Then handle stuff in your update function:

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        Tick t ->
            ( { model
                | transition = Transition.step t model.transition
              }
            , Cmd.none
            )

        StartAnimation newThing ->
            let
                oldThing =
                    Transition.value model.transition
            in
            ( { model
                | transition =
                    Transition.for 600 (interpolateThing oldThing newThing)
              }
            , Cmd.none
            )

Then make your view like normal:

view : Model -> Html Msg
view model =
    viewMyThing (Transition.value model.transition)

viewMyThing : MyThing -> Html Msg
viewMyThing thing =
    --- ...

Transitions


type Transition a

A transition is a smooth interpolation between a beginning state and an end state, with a duration and easing.

for : Basics.Int -> Interpolation.Interpolator a -> Transition a

Create a transition that will run for a certain number of miliseconds. You need to provide an interpolation between the start and end states.

For example to fade something in for 400ms:

fadeIn : Item -> Transition Item
fadeIn item =
    Interpolation.map (\opacity -> { item | opacity = opacity)
        (Interpolation.float 0 1)
        |> Transition.for 400

easeFor : Basics.Int -> Easing -> Interpolation.Interpolator a -> Transition a

This is like Transition.for, but allows one to specify a custom Easing function. Transition.for defaults to Transition.easeCubic.

constant : a -> Transition a

A transition that is already complete that will always return the value passed in.

step : Basics.Int -> Transition a -> Transition a

Updates the internal state forward by the passed number of miliseconds. You would typically do this in your update function.

value : Transition a -> a

Returns the "current" value. You would typically call this in the view and render whatever this returns.

import Interpolation

transition : Transition Int
transition =
    Transition.easeFor 500 Transition.easeLinear (Interpolation.int 0 10)

transition |> Transition.value --> 0
transition |> Transition.step 250 |> Transition.value --> 5
transition |> Transition.step 600 |> Transition.value --> 10

isComplete : Transition a -> Basics.Bool

Allows you to check if a transition has finished running. This can be used to clean up subscriptions.

reverse : Transition a -> Transition a

Reverses the direction of a transition running it backwards.

Repetition

repeat : Basics.Int -> Transition a -> Transition a

Repeat the transition a number of times.

repeatAlternate : Basics.Int -> Transition a -> Transition a

Repeat the transition a set number of times, but run every even run in reverse.

repeatIndefinitely : Transition a -> Transition a

Keep running the transition.

repeatAlternateIndefinitely : Transition a -> Transition a

Keep running the transition indefinitely, but alternating forward and reverse runs.

Staggered animation

stagger : { durationEach : Basics.Int, delay : Basics.Int, easing : Easing } -> List (Interpolation.Interpolator a) -> Transition (List a)

Run a bunch of animations with a duration and easing, but delay each successive animation by the delay amount.

Tip: You may find the List (Interpolator a) -> Transition (List a) a little inconvenient for organizing staggered animations. However, you can create an List (Interpolator (a -> a)), where each function touches and interpolates some orthogonal property, then List.foldl (\fn val -> fn val) model.target (Transition.value model.transition). This way you can stagger updates to almost any datastructure. However, you need to be somewhat careful if there are other ways the datastructure can be changed (like user input) to make sure to interupt the animation suitably.

type Foo =
    { position: (Float, Float)
    , color : Color
    }

[ \t foo -> { foo | position = interpolatePosition t }
, \t foo -> { foo | color = Interpolation.rgb Color.blue Color.red t }
]
|> Transition.stagger { durationEach = 200, delay = 100, easing Transition.easingCubic }

Will first start animating the position, then after 100ms start animating the color, for a total duration of 300ms.

Easing


type Easing

Easing is a method of distorting time to control apparent motion in animation. It is most commonly used for slow-in, slow-out. By easing time, animated transitions are smoother and exhibit more plausible motion.

easeLinear : Easing

Linear easing is esentially the identity function of easing.

easeCubic : Easing

Symetric cubic easing. This is quite a good default for a lot of animation. Equivalent to easePolynomial 3

easePolynomialIn : Basics.Float -> Easing

Polynomial easing; raises t to the provided exponent.

easePolynomialOut : Basics.Float -> Easing

Reverse polynomial easing; equivalent to 1 - easePolynomialIn (1 - t).

easePolynomial : Basics.Float -> Easing

Symmetric polynomial easing. In t [0, 0.5] equivalent to easePolynomialIn in [0.5, 1] easePolynomialOut

easeSinusoidalIn : Easing

Sinusoidal easing; returns sin(t).

easeSinusoidalOut : Easing

Reverse sinusoidal easing; equivalent to 1 - sinIn(1 - t).

easeSinusoidal : Easing

Symmetric sinusoidal easing

easeExponentialIn : Easing

Exponential easing; raises 2 to the exponent 10 * (t - 1).

easeExponentialOut : Easing

Reverse exponential easing.

easeExponential : Easing

Symetric exponential easing.

easeCircleIn : Easing

Circular easing.

easeCircleOut : Easing

Reverse circular easing.

easeCircle : Easing

Symetric circular easing.

easeElasticIn : { amplitude : Basics.Float, period : Basics.Float } -> Easing

Elastic easing, like a rubber band. Reasonable defaults for the parameters would be { amplitude = 1, period = 0.3 }.

easeElasticOut : { amplitude : Basics.Float, period : Basics.Float } -> Easing

Reverse elastic easing.

easeElastic : { amplitude : Basics.Float, period : Basics.Float } -> Easing

Symmetric elastic easing.

easeBackIn : Basics.Float -> Easing

Anticipatory easing, like a dancer bending his knees before jumping off the floor. The degree of overshoot is configurable. A reasonable default is 1.70158. This represents about 10% more than the difference between the numbers.

easeBackOut : Basics.Float -> Easing

Reverse anticipatory easing.

easeBack : Basics.Float -> Easing

Symmetric anticipatory easing.

easeBounceIn : Easing

Bounce easing, like a rubber ball.

easeBounceOut : Easing

Reverse bounce easing.

easeBounce : Easing

Symmetric bounce easing.