tad-lispy / springs / Spring

This module can be used to create and track oscillating values. A value like that will change over time in a way similar to a center of mass attached to an anchored spring. See the ReadMe for an overview, demos and use cases.

Type


type Spring

Let's say we are creating a program that animates the position of an element towards the last click position.

type alias Model =
    { x : Spring
    , y : Spring
    }

Constructor

create : { strength : Basics.Float, dampness : Basics.Float } -> Spring

Create a spring with the value and target set to 0.

Let's say we are creating a program that animates the position of an element toward last click position.

type alias Model =
    { x : Spring
    , y : Spring
    }

init : Flags -> ( Model, Cmd msg )
init flags =
    ( { x = Spring.create { strength = 20, dampness = 4.5 }
      , y = Spring.create { strength = 20, dampness = 4.5 }
      }
    , Cmd.none
    )

Note that the dampness is a logarythmic value: dampness = 5 results in damping ratio 25 times higher than dampness = 1.

Control

setTarget : Basics.Float -> Spring -> Spring

Set a new target (relaxed value) for the spring.

The current value and its velocity will be preserved, so the spring will smoothly transition its movement. Typically you would set a target in response to an event, e.g.:

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        Click x y ->
            ( { model
                | x = Spring.setTarget x model.x
                , y = Spring.setTarget y model.y
              }
            , Cmd.none
            )

Update

animate : Basics.Float -> Spring -> Spring

Gently update the internal state of the spring

Typically you would do it in response to an animation frame message, like this:

subscriptions : Model -> Sub Msg
subscriptions model =
    Browser.Events.onAnimationFrameDelta Animate

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        Animate delta ->
            ( { model
                | x = Spring.animate delta model.x
                , y = Spring.animate delta model.y
              }
            , Cmd.none
            )

Note: that if the frame rate is low, the time passage experienced by the spring will deviate from the real time. See the comment in source code for more details.

jumpTo : Basics.Float -> Spring -> Spring

Forcefully set the value and interrupt the motion.

The target will be preserved, but velocity will be set to 0. It is useful when you set the spring for the first time (e.g. in the init function) or you want to reset the animation.

init : Flags -> ( Model, Cmd msg )
init flags =
    ( { x =
            Spring.create 20 20
                |> Spring.setTarget 200
                |> Spring.jumpTo 200
      , y =
            Spring.create 20 20
                |> Spring.setTarget 200
                |> Spring.jumpTo 200
      }
    , Cmd.none
    )

Query

value : Spring -> Basics.Float

Measure the current value of the spring.

Typically you want to access it in the view function, like this:

Element.el
    [ Element.width (Element.px 20)
    , Element.height (Element.px 20)
    , Font.size 30
    , model.x
        |> Spring.value
        |> Element.moveRight
    , model.y
        |> Spring.value
        |> Element.moveDown
    ]
    (Element.text "\u{1F991}")

Above I use Elm UI Elements and Attributes, but it's not difficult to implement the same behaviour using CSS transformations. The value of a Spring is just a Float - you can do with it whatever you need.

target : Spring -> Basics.Float

Get current target of a spring

Can be useful to see where the spring is going. Maybe you want to display something there?

atRest : Spring -> Basics.Bool

Check if the spring is at rest

It indicates that the spring has reached its target and no motion is going on. Maybe you want to unsubscribe from animation frames? Or remove an element?

subscriptions : Model -> Sub Msg
subscriptions model =
    if Spring.atRest model.x && Spring.atRest model.y then
        Sub.none

    else
        Browser.Events.onAnimationFrameDelta Animate