How to animate multiplayer cursors
Smoothly rendering live cursors is more difficult than it sounds when real‑world conditions are taken into account—here’s a quick introduction to a few different methods, plus some React snippets.
Multiplayer cursors are becoming an increasingly common sight across all sorts of collaborative tools, but have you ever wondered how they’re animated? Animating realtime cursors is more complex than it first may seem, thanks to network and connection limitations. Here’s a quick overview of a few different methods, with some React snippets to get you started. Let’s dive in!
Why do we need animations?
In an ideal world, animations wouldn’t be needed, and every single cursor movement would be reflected immediately across browsers.
However, updates can’t be transmitted instantly, and realistically, we wouldn’t want them to be. Even if updates were sent every millisecond (1ms), blinking your eye still takes longer than 100ms—would it really be efficient to send 100 updates in such a short space of time?
Throttling
Every realtime service will use some kind of throttling to ensure that your code, and their systems, aren’t overloaded, and this is where our animation problem lies. Try adjusting the throttle rate with the range slider below.
Lowering the throttle rate prevents updates being sent as regularly, and as you can see, this results in the cursor animation only being completely smooth at the lowest rates.
In the real world
Of course in the real world, the cursor won’t update exactly on the throttle rate (for example, 120ms) there'll be some slight variation between each update caused by varying processing times on clients and server, as well as changing network latency. The actual time between updates will be slightly different.
I’m going to call the actual time between updates the update rate, a value that includes throttling, and other normal variations. If you’d like to see a realistic update rate, try interacting with the demo below—it connects to Liveblocks and manually measures the rate on each update.
This demo uses Liveblocks’ default throttle rate, but you can specify a custom rate within createClient.
Animation methods
Now we’ve seen the problem, let’s fix it. There are three different ways we can tackle this: CSS transitions, spring animations, and spline animations.
In the React code examples below, we’ll be animating cursors by passing x
and
y
props to the component, these numbers corresponding to the pixel distance
from the top-left corner of the container.
CSS Transitions
The easiest approach to animate cursor locations is to use a CSS transition on
the cursor component which can be added with just a single CSS property,
transition
.
When you increase the update time, you’ll probably notice the most prominent flaw with CSS transitions—the route to the next point is always a straight line. CSS transitions only consider the cursor’s next coordinates, not the path taken to get there, nor the momentum of the cursor.
If you move the slider in the diagram below, you can see the shape of the original cursor’s movement alongside the path created by the transition. Each "x" represents a new update that’s been received.
As you can see, each time an update is received, the transition starts pathing a straight line directly towards the next update; certainly less than ideal.
Timing functions
When it comes to CSS timing functions, interestingly, and perhaps
counterintuitively, linear
transitions result in smoother cursors than
easing
transitions. Try enabling ease-in-out
, then going back to linear
:
But how does that make sense? Transitions such as ease-in-out
slow down
towards the start and end of each transition, but with cursors we don’t want
that—we want a similar speed maintained between updates. If the cursor slows
down on each update, before speeding up again, it won’t look as smooth. Using
linear
ensures that a similar speed is maintained, and the shift to the next
set of coordinates is smoother.
Should I use CSS transitions?
I’d only recommend using CSS transitions if you’re after a lightweight solution that doesn’t use any third-party packages. Here’s an example of a React component that uses CSS transitions:
Springs
Rather than using straight-line CSS transitions, we can look to spring physics to mimic more organic motion. Spring animations allow us to control aspects of cursor movement such as stiffness and damping ratio, lending the appearance of a real item moving with its own impetus and mass, resulting in a much more natural movement.
Spring animations tend to be far smoother than CSS transitions, as they take into account not just the next coordinates, but also the current momentum of the element.
After each update, the cursor’s direction is smoothly changed before it paths a straight-line to the next coordinates.
Should I use spring animations?
If you’re after smooth cursors with a quick response, and strict pathing accuracy isn’t important, spring animations are the way to go. Here’s an example of a spring-animated React cursor built with Framer Motion.
Spring animations in use
A good example of an app using spring-animated cursors is our open-source project Pixel Art Together, which uses Svelte’s built-in spring library.
Splines
Spline interpolation is a method of constructing new points from a set of known points, and plotting a smooth curve between. It’s often used to plot curves in charts between discrete points of data.
We can make use of spline interpolation to animate smooth paths for cursors, relying on it using multiple different points to create a more accurate path. There is a downside to this accuracy however—the function waits to receive multiple points before rendering, so the cursor is slightly delayed.
Spline animations take into account both the previous coordinates, and the next coordinates, to create an accurate path that passes directly through every point, unlike the other animation types.
As you can tell, the path created isn’t 100% accurate—splines still struggle with abrupt changes of direction—but it is much closer than any other method.
Should I use spline animations?
I’d recommend using spline-animated cursors where accuracy is preferred and a little delay is acceptable. The easiest way to do this in React is with the perfect‑cursors library, built by the creator of tldraw.
Spline animations in use
A great example of spline cursors in the wild is tldraw,
which uses perfect‑cursors
.
Comparison
Now that we’ve taken a look the different animation methods, let’s see how they perform side-by-side.
When we compare we can really start to notice the delay caused by spline animations, and also, somewhat surprisingly, we can see just how similar CSS transitions and spring animations are, despite springs feeling much more fluid.
CSS transitions make for a quick-and-easy lightweight solution, whereas spring and spline animations result in a smoother experience. Springs work smoothly, but if you’re after accuracy, and responsiveness isn’t important, take a look into splines!
Looking to add realtime cursors?
This article covers the front end of live cursors, but what about the back end? Implementing this is a much more difficult task, but it doesn’t have to be—you can let Liveblocks handle your collaborative back end for you. We'll do all the heavy lifting, so you can work on building your app.
Cursor positions, along with any other multiplayer data, can be sent across
clients with
updatePresence()
,
part of the @liveblocks/client
package.
Changes to other users’ presence can then be detected on clients using
subscribe("others")
.
We also have @liveblocks/react
, a
special React package that simplifies rendering multiplayer components even
further. You can add
useOthers()
which replaces
the subscription above:
Find your framework
We have working examples of live cursors built in a number of different frameworks to get you started, have a try: Next.js, Vue, Svelte, Solid, JavaScript.
There’s more to life than cursors
Wait, really? This article is about live cursors, but these tips also apply to other animated components in multiplayer environments, for example sticky notes on a collaborative whiteboard. We have plenty of other fun examples too!