LiveAnimate - declarative animations for Phoenix LiveView (SPA-style ergonomics, zero JS deps, plug and play!)

Hello all :wave:

I’m happy to share LiveAnimate, a library for declarative UI animations in Phoenix LiveView.

The idea:
add one <.motion> component to your HEEx and get entrance/exit animations, gestures, spring physics, scroll triggers, FLIP layout animations, and whole-page transitions, all without writing any JavaScript.

<.motion id="hero" animate="slide-up" hover="lift">
Hello, world!
</.motion>

Everything is declared on the element and serialized to the client; the Elixir side stays purely declarative. Animations run on the browser’s Web Animations API (GPU-accelerated), not per-frame JS.

LiveAnimate — entrance, exit & hover

Why another animation lib?

I wrote this mostly to make animation easier. Animation tends to be a tricky area for most people, and other ecosystems have plenty of tooling to smooth it over. My goal was to make it almost plug-and-play and intuitive from Elixir alone, so developers of any seniority can ship animated pages that feel like an SPA, without touching JavaScript.

The part I cared about most is LiveView lifecycle integration — the things that are genuinely awkward to do by hand:

  • Exit animations that play before the server removes the element (via the phx-remove transition binding), so a toast/list item animates out instead of vanishing.
  • FLIP layout animations that survive stream reflows and reorders — items smoothly slide to their new positions.
  • Server-driven page transitions using the View Transitions API — declared on the LiveView, not wired up in JS:

defmodule MyAppWeb.PageLive do
use MyAppWeb, :live_view
use LiveAnimate
@transition "slide-left" # or %{preset: "slide-left", duration: 300, apply_to: :all}
end

Page transitions

Gestures are pointer-based and behave the same on mouse and touch. Drag ships: free or axis-locked, with optional bounds, elastic overscroll, and a velocity-aware spring snap-back that carries the fling’s momentum on release, or snap_back: false to leave it exactly where you dropped it.
Every release pushes its position to the server via phx-drag-end, so swipe-to-dismiss, sliders, and draggable cards are just a few lines of HEEx. And because each gesture animates the individual transform channels, a drag, a hover scale, and a FLIP move all compose instead of fighting over transform.

<.motion id="card" drag={%{axis: "x"}} animate="slide-left" layout={true} phx-drag-end="dismiss">
Swipe me
</.motion>

Drag gestures

Other bits

  • 25+ presets (fade, slide, zoom, flip, plus keyframe/attention ones like shake/pulse/float) — or pass your own inline keyframes / named variants.
  • Spring physics (velocity-aware, so a drag release carries its momentum) or tween easing.
  • in_view / scroll triggers backed by IntersectionObserver (no per-scroll layout thrash on the main thread).
  • No runtime npm dependency. the engine is hand-rolled and ships with the package. Motion One was studied as a reference, never imported. Setup is one hook + one CSS import.
  • Accessible by default. honors prefers-reduced-motion, with a per-element opt-out for decorative motion.

Links

{:live_animate, “~> 0.1.3”}

It’s still early (0.1.3) and I’d love feedback! API ergonomics, missing presets, edge cases on your setup. anything.
Shared-element morphing, scroll-scrubbed animations, and fuller layout projection are on the v2 roadmap, but I’m open to suggestions.
Thanks for taking a look! :heart:

16 Likes

LiveAnimate is nice Lucas - welcome to the forum :023:

1 Like

Thanks Aston!

1 Like