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.

<Figure highlight={false}>
  <figcaption className="sr-only">
    An interactive demo displaying a perfectly animated cursor.
  </figcaption>
  <CursorInterpolationDemo
    name="perfect-world"
    initialInterpolation="none"
    initialFrequency={0}
    hideRange
    hideDropdown
    showHint
  />
</Figure>

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.

<Figure highlight={false}>
  <figcaption className="sr-only">
    An interactive demo allowing you to change the update frequency of cursors.
  </figcaption>
  <CursorInterpolationDemo
    name="ping-exists"
    initialInterpolation="none"
    initialFrequency={120}
    hideDropdown
    showHint
  />
</Figure>

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.

<Figure highlight={false}>
  <figcaption className="sr-only">
    An diagram explaining that update time isn&apos;t just throttling.
  </figcaption>
  <UpdateTimeFigure />
</Figure>

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.

<Figure highlight={false}>
  <figcaption className="sr-only">
    A demo that displays your current update frequency and ping for Liveblocks
    when interacted with.
  </figcaption>
  {/* <TestUpdateFrequency /> */}
</Figure>

This demo uses Liveblocks’ default throttle rate, but you can specify a custom
rate within
[createClient](/docs/api-reference/liveblocks-client#createClientThrottle).

## 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.

```jsx
<Cursor x={156} y={362} />
```

## 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`.

<Figure highlight={false}>
  <figcaption className="sr-only">
    An interactive demo displaying a cursor animated with CSS transitions.
  </figcaption>
  <CursorInterpolationDemo
    name="transitions"
    initialInterpolation="transition"
    hideDropdown
  />
</Figure>

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.

<Figure highlight={false}>
  <figcaption className="sr-only">
    An interactive diagram showing the path taken by a CSS transition animated
    cursor, alongside the original cursor&apos;s path.
  </figcaption>
  <CursorPathDemo interpolation="transition" />
</Figure>

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`:

<Figure highlight={false}>
  <figcaption className="sr-only">
    An interactive demo displaying a cursor animated with CSS transitions.
  </figcaption>
  <CursorInterpolationDemo
    name="transitions"
    initialInterpolation="transition"
    transitionToggle
    hideDropdown
  />
</Figure>

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:

```jsx file="Cursor.jsx" isCollapsed isCollapsable
// CSS transition animated cursor
export default function Cursor({ color, x, y }) {
  return (
    <div
      style={{
        position: "absolute",
        top: "0",
        left: "0",
        transform: `translate(${x}px, ${y}px)`,
        transition: "transform 120ms linear",
      }}
    >
      <CursorSvg color={color} />
    </div>
  );
}

// SVG cursor shape
function CursorSvg({ color }) {
  return (
    <svg width="32" height="44" viewBox="0 0 24 36" fill="none">
      <path
        fill={color}
        d="M5.65376 12.3673H5.46026L5.31717 12.4976L0.500002 16.8829L0.500002 1.19841L11.7841 12.3673H5.65376Z"
      />
    </svg>
  );
}
```

## 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.

<Figure highlight={false}>
  <figcaption className="sr-only">
    An interactive demo displaying a cursor animated with springs.
  </figcaption>
  <CursorInterpolationDemo
    name="springs"
    initialInterpolation="spring"
    hideDropdown
  />
</Figure>

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.

<Figure highlight={false}>
  <figcaption className="sr-only">
    An interactive diagram showing the path taken by a spring animated cursor,
    alongside the original cursor&apos;s path.
  </figcaption>
  <CursorPathDemo interpolation="spring" />
</Figure>

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](https://www.framer.com/motion/).

```jsx file="Cursor.jsx" isCollapsed isCollapsable
import { motion } from "motion/react";

// Spring animated cursor
export default function Cursor({ color, x, y }) {
  return (
    <motion.div
      style={{
        position: "absolute",
        top: "0",
        left: "0",
      }}
      initial={{ x, y }}
      animate={{ x, y }}
      transition={{
        type: "spring",
        damping: 30,
        mass: 0.8,
        stiffness: 350,
      }}
    >
      <CursorSvg color={color} />
    </motion.div>
  );
}

// SVG cursor shape
function CursorSvg({ color }) {
  return (
    <svg width="32" height="44" viewBox="0 0 24 36" fill="none">
      <path
        fill={color}
        d="M5.65376 12.3673H5.46026L5.31717 12.4976L0.500002 16.8829L0.500002 1.19841L11.7841 12.3673H5.65376Z"
      />
    </svg>
  );
}
```

### Spring animations in use

A good example of an app using spring-animated cursors is our open-source
project [Pixel Art Together](https://pixelart.liveblocks.app), which uses
Svelte’s built-in spring library.

<Figure highlight={false}>
  <figcaption className="sr-only">
    Video displaying live cursors and collaboration in Pixel Art Together.
  </figcaption>
  <video loop controls playsInline>
    <source
      src="/images/blog/video-pixel-art-together-drawing.mp4"
      type="video/mp4"
    />
  </video>
</Figure>

## 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.

<Figure highlight={false}>
  <figcaption className="sr-only">
    An graph with points connected by a spline curve.
  </figcaption>
  <SplineGraphExample className="mx-auto w-full max-w-md" />
</Figure>

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.

<Figure highlight={false}>
  <figcaption className="sr-only">
    An interactive demo displaying a cursor animated with splines.
  </figcaption>
  <CursorInterpolationDemo
    name="splines"
    initialInterpolation="spline"
    hideDropdown
  />
</Figure>

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.

<Figure highlight={false}>
  <figcaption className="sr-only">
    An interactive diagram showing the path taken by a spline animated cursor,
    alongside the original cursor&apos;s path.
  </figcaption>
  <CursorPathDemo interpolation="spline" />
</Figure>

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](https://github.com/steveruizok/perfect-cursors) library, built
by the creator of
[tldraw](/blog/tldraw-became-viral-by-converting-its-product-to-multiplayer-using-liveblocks).

```jsx file="Cursor.jsx" isCollapsed isCollapsable
import {
  useCallback,
  useEffect,
  useRef,
  useLayoutEffect,
  useState,
} from "react";
import { PerfectCursor } from "perfect-cursors";

// Spline animated cursor
export default function Cursor({ color, x, y }) {
  const rCursor = useRef(null);

  const animateCursor = useCallback((point) => {
    const elm = rCursor.current;
    if (!elm) return;
    elm.style.setProperty(
      "transform",
      `translate(${point[0]}px, ${point[1]}px)`
    );
  }, []);

  const onPointMove = usePerfectCursor(animateCursor);

  useEffect(() => onPointMove([x, y]), [onPointMove, x, y]);

  return (
    <div
      ref={rCursor}
      style={{
        position: "absolute",
        top: "0",
        left: "0",
      }}
    >
      <CursorSvg color={color} />
    </div>
  );
}

// Hook for adding perfect-cursors
function usePerfectCursor(cb, point) {
  const useIsomorphicLayoutEffect =
    typeof window !== "undefined" ? useLayoutEffect : useEffect;

  const [pc] = useState(() => new PerfectCursor(cb));

  useIsomorphicLayoutEffect(() => {
    if (point) {
      pc.addPoint(point);
    }
    return () => pc.dispose();
  }, [pc]);

  return useCallback((point) => pc.addPoint(point), [pc]);
}

// SVG cursor shape
function CursorSvg({ color }) {
  return (
    <svg width="32" height="44" viewBox="0 0 24 36" fill="none">
      <path
        fill={color}
        d="M5.65376 12.3673H5.46026L5.31717 12.4976L0.500002 16.8829L0.500002 1.19841L11.7841 12.3673H5.65376Z"
      />
    </svg>
  );
}
```

### Spline animations in use

A great example of spline cursors in the wild is [tldraw](https://tldraw.com),
which uses `perfect‑cursors`.

<Figure highlight={false}>
  <figcaption className="sr-only">
    Video displaying live cursors and collaboration in tldraw.
  </figcaption>
  <video loop controls playsInline>
    <source
      src="/images/blog/video-tldraw-spline-cursors.mp4"
      type="video/mp4"
    />
  </video>
</Figure>

## Comparison

Now that we’ve taken a look the different animation methods, let’s see how they
perform side-by-side.

<Figure highlight={false}>
  <figcaption className="sr-only">
    An interactive demo displaying all animated cursors at once.
  </figcaption>
  <CursorInterpolationDemo name="all" initialInterpolation="all" hideDropdown />
</Figure>

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.

<Figure highlight={false}>
  <figcaption className="sr-only">
    An interactive diagram showing the paths taken by all different animated
    cursors, alongside the original cursor&apos;s path.
  </figcaption>
  <CursorPathDemo interpolation="all" hidePaths />
</Figure>

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()`](/docs/api-reference/liveblocks-client#Room.updatePresence),
part of the [`@liveblocks/client`](/docs/api-reference/liveblocks-client)
package.

```js
// Send cursor coordinates on movement
function onPointerMove(event) {
  room.updatePresence({
    cursor: {
      x: event.clientX,
      y: event.clientY,
    },
  });
}

// Remove cursor position when not on page
function onPointerLeave() {
  room.updatePresence({
    cursor: null,
  });
}
```

Changes to other users’ presence can then be detected on clients using
[`subscribe("others")`](https://liveblocks.io/docs/api-reference/liveblocks-client#Room.subscribe.others).

```js
// Callback runs on presence updates
room.subscribe("others", (others) => {
  // Array of objects containing user data
  others.toArray();
});
```

We also have [`@liveblocks/react`](/docs/api-reference/liveblocks-react), a
special React package that simplifies rendering multiplayer components even
further. You can add
[`useOthers()`](/docs/api-reference/liveblocks-react#useOthers) which replaces
the subscription above:

```jsx
function LiveCursors() {
  const others = useOthers();

  return (
    <div>
      {others.map((other) => {
        /* Cursor component */
      })}
    </div>
  );
}
```

### 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](/examples/live-cursors/nextjs-live-cursors),
[Vue](/examples/live-cursors/vuejs-live-cursors),
[Svelte](/examples/live-cursors/sveltekit-live-cursors),
[Solid](/examples/live-cursors/solidjs-live-cursors),
[JavaScript](/examples/live-cursors/javascript-live-cursors).

### 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](/docs/tutorials/collaborative-online-whiteboard). We
have plenty of other fun [examples](/examples) too!