We have shipped a lot of motion in the last few years. Lottie for After Effects exports. CSS keyframes for the small stuff. GSAP when we needed proper timeline control. Each one has a job it does well, and we still use all of them.

What kept being awkward was interactive animation. Anything where the design has to react to a click, a hover, a value coming back from the page. The handoff between motion designer and developer usually turned into screenshots, JSON tweaks, and a bunch of “can you make it slow down here” comments.

Rive is the first tool that made that loop feel sane for us, and it has quietly gotten good enough to use on production websites, not just demos. This post is about where it fits, and the parts that are still on us as the developer.

What Rive is actually doing differently

The short version: instead of exporting a frozen animation and triggering it from code, you build a small state machine inside the file. Inputs. Transitions. Conditions. The animation knows how to behave on its own.

That changes the handoff. The designer can hand off a .riv file with named inputs like isHovering or mood, and the website just sets those values. No more reverse engineering what the timeline was supposed to do.

A few practical things that make Rive worth picking up:

  • State machines inside the file. Logic lives next to the motion, not scattered across JS.
  • Named inputs and triggers. A clean contract between design and code.
  • Vector at any size. Same .riv looks right on a 13 inch laptop and a 5K monitor.
  • Reasonable file sizes. Most of our characters land between 10KB and 80KB.
  • One source file, many platforms. Web, iOS, Android, Flutter, game engines.

Something live, not a stock demo

Here is one Lupian-owned file from our demo set. Advent Adventure is a better first reference here because it shows that Rive can be more than a character loop: small scene, clickable bits, hoverable parts, still just one .riv file.

The Triangle Mascot post uses a smaller character file if you want the cleaner embed details and state machine setup.

What is still the website’s job

The Rive file is only half the story. The site still has to play nice with it. Things we keep hitting:

  • Runtime choice. @rive-app/webgl2 for full feature support like mesh deformations and feathering. @rive-app/canvas when bundle size matters more than effects.
  • Canvas sizing. Always call resizeDrawingSurfaceToCanvas() from onLoad, never set canvas.width or canvas.height manually. That second one will break the renderer in ways that look like a corrupt file.
  • Resize handling. Window resize, orientation change, layout shifts. All need to ping the canvas again.
  • Cleanup. Call riveInstance.cleanup() when the component unmounts. The runtime holds C++ objects under the hood.
  • Multiple instances on a page. Set useOffscreenRenderer: true so they share a renderer instead of each spinning up their own.
  • Fallback. SSR, slow connections, blocked third party domains. Decide what shows up if the file fails or takes too long.
  • QA. Test on iOS Safari early. It tends to be where the surprises live.

None of that is hard, but none of it ships itself. This is why we treat Rive embeds as a real component with a contract, not a one-off <canvas> drop in.

Where this fits at Lupian

We use Rive for character moments, onboarding, dashboard widgets, hero animations, and small interactive details that would have been Lottie or video before. The demo site has a few of these running with data binding wired in. The dev site is where we break things on purpose.

If you want to see how it slots into a marketing site, the main Lupian site uses these patterns end to end.

What is coming next

Next post drills into the Triangle Mascot file. After that we want to write up data binding properly, multi instance performance, and a few notes on the scripting API. If there is something specific you want us to cover, drop us a line.