I’d like to share Elm-Motion, a library for animations and scrolling built around one idea: define an animation or scroll once, then run it on whichever engine fits - without rewriting the animation or scroll.
Build once, swap engines
The animation definition is engine-agnostic. The same fadeIn runs on any engine:
-- Define it once - reusable, engine-agnostic
fadeIn : AnimBuilder { eng | withTiming : () } -> AnimBuilder { eng | withTiming : () }
fadeIn =
Opacity.begin
>> Opacity.to 1
>> Opacity.duration 2500
>> Opacity.end
-- Run it with the Transition engine (pure CSS, minimal setup)
Transition.animate model.animState <|
Transition.for "box"
>> fadeIn
-- ...or the Keyframe engine (CSS @keyframes) - same `fadeIn`, no rewrite
Keyframe.animate model.animState <|
Keyframe.for "box"
>> fadeIn
-- ...or the Sub engine (pure Elm, frame-based) - same `fadeIn`, no rewrite
Sub.animate model.animState <|
Sub.for "box"
>> fadeIn
-- ...or the WAAPI engine (JS Web Animations API) - same `fadeIn`, no rewrite
WAAPI.animate model.animState <|
WAAPI.for "box"
>> fadeIn
Some engines have more capabilities than others - the type system and compiler keep you honest about what each one supports.
The same scroll configuration works across all three.
Pure Elm vs JS interop
Pure Elm, no setup: Transition, Keyframe, Sub.
Needs JS companion (@phollyer/elm-motion, on npm + CDN): WAAPI, ScrollTimeline, ViewTimeline — these drive the browser’s Web Animations / scroll-timeline APIs through ports.
Full docs + interactive examples for every engine: Elm Motion
Status
It’s not published yet - I can’t possibly test every edge case or use, so if you’d like to have a play, any and all feedback would really be appreciated.
Looks fantastic and very comprehensive approach to animation in Elm. Always been a pain when you wanted to add some “magic” in your pages… How easily can this be integrated within an elm-ui app for example?
It’s completely view agnostic. So all you need to do is use Elm-UI’s helper functions:
engineAttrs : Model -> List (Element.Attribute msg)
engineAttrs model =
-- swap 'Engine' for your chosen engine
Engine.attributes "animGroupName" model.animState
|> List.map Element.htmlAttribute
Thank you Paul, impressive work. Always amazed by the quality of the Elm lang & its eco-system. Will share more when I get an opportunity to implement in a project.
Welcome to the community. It’s not complete yet, I’m still testing various scenarios etc, so let me know if you find any animations or scrolls behaving badly. Or if the docs aren’t clear about how to do ‘x’ etc.
I did not played with it so I can’t provide useful feedback. But I like the API shown in examples. The fact that you model transition as function seems like idea worth exploring. It looks like a lot of though and care went into this.
I’ve always enjoyed animating, ever since falling in love with Flash in the days of Macromedia.
Elm-Motion came about because I wanted to do some CSS Transitions and also use the JS Web Animations API in a toy project. I also dislike repeating myself from project to project so started it as a little local private library.
As it developed, I realised it could have potential so, yes, I’ve spent a lot of time on it - wanting it to be simple enough for beginners to Elm, but also able to express complex animations in a simple but solid way. So far, I think it’s solid for multi-property animations per element, but I don’t know if it’s there yet wrt to seriously complex animations and as I’ve had to take a step back for a few weeks due to personal circumstances I figured it was a good time to put it out to the community for feedback.
Nothing is set in stone, and would like to be sure it’s all good, and wanted, before I publish.
I’ve just fixed the sequencing examples, somehow the examples and accompanying code became out of sync on the mkdocs site, so if you previously looked at them and thought - Uh?, the code examples are now correct for the observable examples.
Also fixed a WAAPI regression bug that stopped the reset and restart functions from doing their thing. The JS companion is now at v1.0.3 on npm.
The repo pre-publish versioning is now with tags, first tag pushed: v1.0.0-alpha.1
My only concern was confusion of too many options. I couldn’t tell the difference between the tabs, I’m unaware of what each of them do and I couldn’t tell from looking at your documentation which one I should use.
Things like motion are really hard to understand and to get right on top of that, so as it currently stands I’d be too scared trying to implement what you have.
Possibly you could split it into basic / advanced modes, I’d happily be guided by you which one I should use and only start looking at the others if I want to go looking at the advanced page where the differences matter.
It’s the first time I’ve written docs and in trying to make them as simple as possible, I may have over complicated them instead. Obviously they make sense to me, so thanks for this.
The tabs are simply working examples, or code examples, for each Engine to demonstrate any differences. If you couldn’t see any differences, that’s intentional - it shows the same code works for each Engine, and switching Engines doesn’t involve any major code changes. So if you create a CSS transition animation, and then want to switch to using the JS Web Animations API, your existing animations should work fine without changes. The only impact on your code would be wiring up the JS companion with ports, adding subscriptions and then adjust the trigger points, and maybe any event handling if you’re listening for animation events - the compiler will help with this process.
It’s really just a matter of deciding what type of animation you want to implement - CSS Transitions, Keyframes, subscription driven, or the JS Web Animations API etc, then use the relevant Engine. The Engines Overview page has a feature comparison table to help select the Engine with the capabilities you need.
If you want to start simple, use the Transition Engine to create CSS transitions - it’s the quickest and simplest to use.
Thanks for the feedback, and feel free to reach out with any questions either here, or by DM