Perfect interpolation for multiplayer cursors.

Overview

perfect-cursors

Perfect interpolation for animated multiplayer cursors. Used in tldraw.

💕 Love this library? Consider becoming a sponsor.

Edit perfect-cursors-demo

Installation

yarn add perfect-cursors
# or
npm i perfect-cursors

Introduction

You can use this library to smoothly animate a cursor based on limited information.

Kapture 2022-01-08 at 09 25 50

Above: We are updating the red cursor's position once every 80 milliseconds. The perfect-cursors library is being used to correctly animate between these positions.

Animating between points

When implementing a multiplayer app, you will most likely be displaying each user's cursor location based on the information you receive from a multiplayer service such as Pusher, Liveblocks.

In a perfect world, these updates would occur "in real time": that is, arriving with zero latency and arriving at the same rate as the user's monitor.

Kapture 2022-01-08 at 09 35 34

Above: Updating the cursor instantly.

In the real world, however, services often "throttle" their updates to roughly one update every 50-80 milliseconds.

Updating the cursors' locations directly will cause cursors to "jump" between points.

Kapture 2022-01-08 at 09 22 43

Above: Updating the cursor's once position every 80 milliseconds.

Transitioning between points via CSS can work, however this looks increasingly artificial as the delay increases. Worse, because updates do not come in on an exact interval, some animations will never finish while others will pause between animations.

Kapture 2022-01-08 at 09 31 55

Above: Transitioning the cursor's once position every 80 milliseconds.

Smart animating with JavaScript and dynamic durations can be better, however this still looks artificial as the delay increases.

Kapture 2022-01-08 at 10 11 39

Above: Animating the cursor once position every 80 milliseconds.

For best results, you would animate while interpolating the cursors' locations based on a set of connected curves (e.g. a "spline").

Kapture 2022-01-08 at 09 25 50

Above: Animating the cursor once position every 80 milliseconds using spline interpolation.

In this way, your animations can very closely approximate the actual movement of a cursor. So closely, in fact, that it appears as though the cursor is being updated "in real time" with a "one-update" delay.

Usage

Quick n' dirty docs.

Usage

To use the library directly, create an instance of the PerfectCursor class and pass it a callback to fire on each animation frame.

import { PerfectCursor } from "perfect-cursors"

const elm = document.getElementById("cursor")

function updateMyCursor(point: number[]) {
  elm.style.setProperty("transform", `translate(${point[0]}px, ${point[1]}px)`)
}

const pc = new PerfectCursor(updateMyCursor)

Use the instance's addPoint to add a point whenever you have a new one.

pc.addPoint([0, 0])
setTimeout(() => pc.addPoint([100, 100]), 80)
setTimeout(() => pc.addPoint([200, 150]), 160)

Use the dispose method to clean up any intervals.

pc.dispose()

Usage in React

To use the library in React, create a React hook called usePerfectCursor.

// hooks/usePerfectCursor

import { PerfectCursor } from "perfect-cursors"

export function usePerfectCursor(
  cb: (point: number[]) => void,
  point?: number[]
) {
  const [pc] = React.useState(() => new PerfectCursor(cb))

  React.useLayoutEffect(() => {
    if (point) pc.addPoint(point)
    return () => pc.dispose()
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [pc])

  const onPointChange = React.useCallback(
    (point: number[]) => pc.addPoint(point),
    [pc]
  )

  return onPointChange
}

And then a Cursor component that looks something like this:

// components/Cursor

import * as React from "react"
import { usePerfectCursor } from "../hooks/usePerfectCursors"

export function Cursor({ point }: { point: number[] }) {
  const rCursor = React.useRef<SVGSVGElement>(null)

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

  const onPointMove = usePerfectCursor(animateCursor)

  React.useLayoutEffect(() => onPointMove(point), [onPointMove, point])

  return (
    <svg
      ref={rCursor}
      style={{
        position: "absolute",
        top: -15,
        left: -15,
        width: 35,
        height: 35,
      }}
      xmlns="http://www.w3.org/2000/svg"
      viewBox="0 0 35 35"
      fill="none"
      fillRule="evenodd"
    >
      <g fill="rgba(0,0,0,.2)" transform="translate(1,1)">
        <path d="m12 24.4219v-16.015l11.591 11.619h-6.781l-.411.124z" />
        <path d="m21.0845 25.0962-3.605 1.535-4.682-11.089 3.686-1.553z" />
      </g>
      <g fill="white">
        <path d="m12 24.4219v-16.015l11.591 11.619h-6.781l-.411.124z" />
        <path d="m21.0845 25.0962-3.605 1.535-4.682-11.089 3.686-1.553z" />
      </g>
      <g fill={"red"}>
        <path d="m19.751 24.4155-1.844.774-3.1-7.374 1.841-.775z" />
        <path d="m13 10.814v11.188l2.969-2.866.428-.139h4.768z" />
      </g>
    </svg>
  )
}

When the user's cursor point changes, pass that information to the Cursor component.

Development

To start the development server:

  • clone this repo
  • run yarn or npm install to install dependencies
  • run yarn start or npm run start from the project root.
  • open localhost:5420 in your browser to view the example project.

Community

License

This project is licensed under MIT.

If you're using the library in a commercial product, please consider becoming a sponsor.

Author

You might also like...

iX is a design system for designers and developers, to consistently create the perfect digital experience for industrial software products.

Siemens Industrial Experience (iX) monorepo iX is an open source design system for designers and developers, to consistently create the perfect digita

Dec 26, 2022

An api named Crypto Versus, a multiplayer versus hacking simulator. Project still in the making!

Crypto Versus A Multiplayer Versus Hacking Simulation Inspired by the Steam game Bitburner Table of content Possible ouputs for all endpoints routes /

Jan 29, 2022

A serverless, real-time, wordle-inspired, multiplayer game.

A serverless, real-time, wordle-inspired, multiplayer game.

fivebysix.com A multiplayer, worlde-inspired web app. Demo Technologies 100% TypeScript (including IAC via CDK) Vite React / Redux AWS AppSync Dynamo

Jan 1, 2023

A basic implementation of Flappy Bird with real-time multiplayer using Hathora

A basic implementation of Flappy Bird with real-time multiplayer using Hathora

Multiplayer Flappy Bird with Hathora A basic implementation of Flappy Bird with real-time multiplayer Overview This is a simple game of Flappy Bird ex

Jul 1, 2022

rGUI is a GUI Library made for the GTA Multiplayer Modification RAGE:MP

rGUI is a GUI Library made for the GTA Multiplayer Modification RAGE:MP

rGUI - RAGE:MP A multifunctional GUI Library made for the GTA Multiplayer Modification RAGE:MP which is easy to use and understand. Will be updated fr

Jan 3, 2023

Spacecraft - multiplayer code editor & terminal

Spacecraft - code, create and hop together. Inspiration Cloud developer environments are the new cool. Services like Gitpod & Github Codespaces have h

Dec 21, 2022

API, web and mobile application for finding a partner to play online multiplayer games.

API, web and mobile application for finding a partner to play online multiplayer games.

Duo Finder Duo Finder is a simple mobile and web application for gamers looking for partners to play a game with. It's basics was developed during the

Sep 20, 2022

A simple library for Node and the browser that allows you to rapidly develop stateful, socketed multiplayer games and web applications.

gameroom.js Overview gameroom.js is a simple library for Node and the browser that allows you to rapidly develop stateful, socketed multiplayer games

Nov 3, 2022

An online multiplayer IO-like game that tests your general knowledge.

An online multiplayer IO-like game that tests your general knowledge.

Source code for insort.app Insort is a game where you sort a deck of cards by some attribute. It's main components are React, Socket.io, Express and P

Dec 10, 2022
Comments
  • Open to decoupling perfect-cursors from @tldraw?

    Open to decoupling perfect-cursors from @tldraw?

    The number and size of JS dependencies required by this (awesome!) library are pretty huge relative to what it actually does. Installing from npm requires installing particular versions of react, react-dom, and cypress, to name a few. Makes it pretty hard to consider using in a non-tldraw application.

    Looks like importing Vec from @tldraw is the primary culprit. I wonder if it's worth copying over Vec, though I know that's not as clean as a direct import. I'm obviously open to other approaches, and I'd be happy to submit a PR if it would be considered.

    opened by brycepj 1
Owner
Steve Ruiz
Design and dev for creative tools.
Steve Ruiz
Hemsida för personer i Sverige som kan och vill erbjuda boende till människor på flykt

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

null 4 May 3, 2022
Kurs-repo för kursen Webbserver och Databaser

Webbserver och databaser This repository is meant for CME students to access exercises and codealongs that happen throughout the course. I hope you wi

null 14 Jan 3, 2023
This is a simple javascript file that gives you control over the browser cursor, alowing for fully animated cursors using CSS's cursor functionality.

animatedWebCursors.js This is a simple javascript file that gives you control over the browser cursor, alowing for fully animated cursors using CSS's

alienmelon 32 Dec 25, 2022
🗂 The perfect Front-End Checklist for modern websites and meticulous developers

Front-End Checklist The Front-End Checklist is an exhaustive list of all elements you need to have / to test before launching your website / HTML page

David Dias 63.6k Jan 7, 2023
Perfect SvelteKit dark mode in 2 lines of code. Support System preference and any other theme with no flashing

This library is a port of next-theme for SvelteKit. All credit goes to pacocoursey and all next-themes contributors While usable, this library is stil

null 42 Sep 30, 2022
cpace - nodemon for C/C++ files. Monitor for any changes in your [.c] and [.cpp] application and automatically restart it - perfect for development

cpace cpace is a tool that helps develop [.c] and [.cpp] based applications by automatically restarting them when file changes are detected. The packa

null 17 Dec 3, 2022
Simple "everyday CRUD" Postgres queries with perfect TypeScript types

Crudely Typed Simple "everyday CRUD" Postgres queries with perfect TypeScript types. Zero dependencies. Designed to work with pg-to-ts and node-postgr

Dan Vanderkam 26 Dec 26, 2022
A remote nodejs Cache Server, for you to have your perfect MAP Cache Saved and useable remotely. Easy Server and Client Creations, fast, stores the Cache before stopping and restores it again!

remote-map-cache A remote nodejs Cache Server, for you to have your perfect MAP Cache Saved and useable remotely. Easy Server and Client Creations, fa

Tomato6966 8 Oct 31, 2022
Simple jQuery plugin that will allow users to zoom in your images, perfect for product images and galleries.

Image Zoom (jQuery) Plugin Simple jQuery plugin that will allow users to zoom in your images, perfect for product images and galleries that is less th

Mario Duarte 8 Aug 3, 2022
A robust and light-weight inventory management application designed to help businesses maintain perfect control over every unit of stock.

Inventory Buddy Access inventory anytime on web, tablet or mobile. Inventory Buddy is a robust and light-weight inventory management application desig

Brynn Smith 7 Nov 5, 2022