{"id":26397247,"url":"https://github.com/tad-lispy/springs","last_synced_at":"2025-03-17T12:17:01.047Z","repository":{"id":62419566,"uuid":"173324383","full_name":"tad-lispy/springs","owner":"tad-lispy","description":"A mirror of https://gitlab.com/tad-lispy/elm-springs","archived":false,"fork":false,"pushed_at":"2019-03-26T22:07:27.000Z","size":390,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-04-14T19:22:18.717Z","etag":null,"topics":["animations","elm","physics","springs"],"latest_commit_sha":null,"homepage":"https://package.elm-lang.org/packages/tad-lispy/springs/latest/","language":"Elm","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tad-lispy.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-03-01T15:25:57.000Z","updated_at":"2023-01-02T06:31:04.000Z","dependencies_parsed_at":"2022-11-01T17:01:14.114Z","dependency_job_id":null,"html_url":"https://github.com/tad-lispy/springs","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tad-lispy%2Fsprings","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tad-lispy%2Fsprings/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tad-lispy%2Fsprings/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tad-lispy%2Fsprings/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tad-lispy","download_url":"https://codeload.github.com/tad-lispy/springs/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244031152,"owners_count":20386534,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["animations","elm","physics","springs"],"created_at":"2025-03-17T12:17:00.347Z","updated_at":"2025-03-17T12:17:01.028Z","avatar_url":"https://github.com/tad-lispy.png","language":"Elm","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Elm Springs\n\nA rough model of a mass attached to a spring, as described by [Hooke's law](https://en.wikipedia.org/wiki/Hooke's_law). Good for making smooth and organic looking animations or modelling oscillating values (e.g. emotions). High physical accuracy is not a priority - performance and API simplicity is more important.\n\n![Logo](https://tad-lispy.gitlab.io/elm-springs/assets/elm-springs-logo.png)\n\n## Demos\n\n  - **[Sliding menu][]**  \n    [source code](https://gitlab.com/tad-lispy/elm-springs/blob/master/demos/src/Examples/SlidingMenu.elm)\n\n    Probably the most practical one at the moment :-)\n\n\n  - **[Oscillometer][]**  \n    [source code](https://gitlab.com/tad-lispy/elm-springs/blob/master/demos/src/Examples/Oscillator.elm)\n\n    A little tool for experimenting with the properties (strength and damping ratio)\n\n\n  - **[Squid game][]**  \n    [source code](https://gitlab.com/tad-lispy/elm-springs/blob/master/demos/src/Examples/Squid.elm)\n\n    A terrifying squid 🦑 Run!\n\n\n  - **[Button][]**  \n    [source code](https://gitlab.com/tad-lispy/elm-springs/blob/master/demos/src/Examples/Button.elm)\n\n    Complete code of the program discussed in [the Use section below](#use), including the come-back behaviour.\n\n\n## Install\n\n```sh\nelm install tad-lispy/springs\n```\n\n\n## Use\n\n\u003e I assume you are familiar with the Elm architecture and can setup a program using `Browser.element` or `Browser.application`. If not, best read [the official guide](https://guide.elm-lang.org/) first.\n\nImport the `Spring` module exposing the type. Typically you would want to also import `Browser.Events` for it's `onAnimationFrameDelta` subscription. You will see below.\n\n```elm\nimport Spring exposing (Spring)\nimport Browser.Events\n```\n\nIn your model, where you would use `Float`, use `Spring` instead. In this case the spring will represent the size of a button in percent.\n\n```elm\ntype alias Model =\n    { size : Spring\n    }\n\ninit : () -\u003e ( Model, Cmd Msg )\ninit () =\n    ( { size =\n            Spring.create\n                { strength = 100\n                , dampness = 2\n                }\n                |\u003e Spring.setTarget 100\n      }\n    , Cmd.none\n    )\n```\n\nThe stronger the spring, the faster it will go, but also there will be more oscillation cycles (it will wobble more). Eventually it should come to a rest. How soon it will stop depends on a damping ratio (I call it `dampness` for short). Good values for dampness are between 0 (it will oscillate forever) and 5 (it will stop pretty much as soon as it reaches the target). So these two parameters together dictate the motion characteristic of a spring. You can experiment with them here: https://tad-lispy.gitlab.io/elm-springs/Oscillator.html\n\nSpring has a target value towards which it will move. Initially it's 0, so if you want another target, set it explicitly like in the example above.\n\nInitial value of a spring is also 0. If you want the spring to immediately jump to a certain value, use `Spring.jumpTo : Float -\u003e Spring -\u003e Spring`. It's often used in `init` or if you want to abruptly terminate the animation. Often you will set the target and jump to it at the same time, like this:\n\n```elm\nSpring.create { strength = strength, dampness = dampness}\n    |\u003e Spring.setTarget 100\n    |\u003e Spring.jumpTo 100\n```\n\nYou can always get the current value of a spring. Most likely you will want to do it in view function. To make a button that when clicked changes its size in a wobbly fashion, you could write a code like this:\n\n```elm\nwobblyButton : Model -\u003e Html Msg\nwobblyButton model =\n    div\n        [ style \"width\" \"100px\"\n        , style \"height\" \"100px\"\n        , model.size\n            |\u003e Spring.value\n            |\u003e (\\value -\u003e value / 100)\n            |\u003e String.fromFloat\n            |\u003e (\\value -\u003e \"scale(\" ++ value ++ \")\")\n            |\u003e style \"transform\"\n        , onClick Click\n        ]\n        []\n```\n\n\u003e **Hint**: It's not directly related to Springs, but if you want your animations to run smoothly, try using CSS transformations (like shown above), instead of changing properties like `width`, `height`, `padding`, `margin`, etc. That way the browser won't have to recalculate layout, which is pretty tedious work and will slow your program down.\n\n\u003e **Hint 2**: Perhaps you have noticed that there is a funny business going on. First we set the motion of the spring be between 0 and 100 and then we divide the value by 100. Why not just set it between 0 and 1? It's because of the equilibrium detection system. With low targets and values it may consider your spring to be in equilibrium while it's still visibly vibrates, and abruptly stop the motion. You will avoid this kind of visual glitch by working with larger targets, even if it means scaling them down later.\n\nLet's say that we want to animate the button in response to the `click` event. In the `update` function change the target to the desired final value, like this:\n\n```elm\nupdate : Msg -\u003e Model -\u003e ( Model, Cmd Msg )\nupdate msg model =\n    case msg of\n        Click -\u003e\n            ( { model | size = Spring.setTarget 0 model.size }\n            , Cmd.none\n            )\n\n        Animate delta -\u003e\n            ( { model | size = Spring.animate delta model.size }\n            , Cmd.none\n            )\n```\n\nThis way, whenever receiving the `Click` message, `update` will change the target of the spring to 0. This will make the button eventually disappear, but first it will shrink and wobble for some time.\n\nBut for its value to actually change over time the program needs to periodically call `Spring.animate : Float -\u003e Spring -\u003e Spring`. This function keeps track of the internal properties of the spring, like its momentum. All the magic is happening there. The `animate` function is taking a `Float` number (often called *delta*) representing the amount of time that passed since previous call (it's a little bit more complex than that, see the API docs for details). The *delta* is typically a number of milliseconds and the easiest way to get it is to subscribe to `animation frame` events, like this:\n\n```elm\nsubscriptions : Model -\u003e Sub Msg\nsubscriptions model =\n    if Spring.atRest model.size then\n        Sub.none\n\n    else\n        Browser.Events.onAnimationFrameDelta Animate\n```\n\nNote that when all the springs are at rest, it's best to cancel the subscription (like above). Otherwise your program will waste significant amount of CPU cycles which may drain the batteries of mobile devices (and contribute to pollution and climate change).\n\nNote that you can use the same function to detect that the animation is finished. Let's say that we want to detect when the button is completely gone and give it a second chance:\n\n```elm\nupdate : Msg -\u003e Model -\u003e ( Model, Cmd Msg )\nupdate msg model =\n    case msg of\n        Animate delta -\u003e\n            ( { model\n                | size =\n                    model.size\n                        |\u003e Spring.animate delta\n                        |\u003e (\\spring -\u003e\n                                if\n                                    (Spring.target spring == 0)\n                                        \u0026\u0026 Spring.atRest spring\n                                then\n                                    Spring.setTarget 100 spring\n\n                                else\n                                    spring\n                           )\n              }\n            , Cmd.none\n            )\n```\n\nWhat a splendid come back! Just as it seemed that it's gone forever, it popped back to life. Proud little button!\n\nThat's really all there is to it. For inspiration take a look at example programs built using springs.\n\n\n## Some more theory\n\nA `Spring` value is a model of a mass attached to a spring. The spring is anchored to a moving `target`. The mass is constant (1).\n\nAs the spring is animated, its centre of mass moves according to the forces acting on in and its momentum. Because the target can be moved while the mass is in motion, the spring is a good driver for animations that can smoothly transition one into another based on events that happen during the animation.\n\nThe `value` represents the current position of the mass. It is re-calculated (together with velocity) by `animate` function and can be retrieved with `value` function.\n\nThe `strength` is how strongly the spring pulls toward target. It is also called the stiffness but I find the former term more intuitive.\n\nThe `dampness` is how resistant the spring is to change in its stretch (both stretching out and contracting in). If dampness is low relative to strength, then the animation will end in long period of vibration around the target value - in other words lowering dampness will increase wobbliness. Setting dampness to 0 will result in something like a sine wave oscillator (but it's not advised to depend on its accuracy).\n\nTarget is the value toward which the mass is pulled. Typically the spring will start in an equilibrium position (i.e. value == target) and later on (due to an event) the target will be changed and the value will follow according to the strength and dampness of the spring.\n\nValue is where the mass is. It can be extracted from the spring using `value` function and set (with `setValue` function - rarely useful).\n\nVelocity is an internal property that cannot be directly modified or read.\n\n\n## Thank you!\n\nThanks for your interest in this library. Feel free to open an issue or merge request. You can also reach to me on Elm Slack (`@lazurski`) with any questions. I'm usually happy to chat. If you build something with it please let me know.\n\n\n[Button]: https://tad-lispy.gitlab.io/elm-springs/Button.html\n[Sliding menu]: https://tad-lispy.gitlab.io/elm-springs/SlidingMenu.html\n[Oscillometer]: https://tad-lispy.gitlab.io/elm-springs/Oscillator.html\n[Squid game]: https://tad-lispy.gitlab.io/elm-springs/Squid.html\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftad-lispy%2Fsprings","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftad-lispy%2Fsprings","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftad-lispy%2Fsprings/lists"}