Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/steveruizok/perfect-cursors
Perfect interpolation for multiplayer cursors.
https://github.com/steveruizok/perfect-cursors
Last synced: 12 days ago
JSON representation
Perfect interpolation for multiplayer cursors.
- Host: GitHub
- URL: https://github.com/steveruizok/perfect-cursors
- Owner: steveruizok
- License: mit
- Created: 2022-01-07T21:41:41.000Z (almost 3 years ago)
- Default Branch: main
- Last Pushed: 2022-01-08T16:02:04.000Z (almost 3 years ago)
- Last Synced: 2024-10-14T21:08:12.137Z (28 days ago)
- Language: TypeScript
- Size: 347 KB
- Stars: 609
- Watchers: 4
- Forks: 13
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE.md
Awesome Lists containing this project
README
# perfect-cursors
Perfect interpolation for animated multiplayer cursors. Used in [tldraw](https://tldraw.com).
💕 Love this library? Consider [becoming a sponsor](https://github.com/sponsors/steveruizok?frequency=recurring&sponsor=steveruizok).
[![Edit perfect-cursors-demo](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/perfect-cursors-demo-u85tu?fontsize=14&hidenavigation=1&theme=dark)
## Installation
```bash
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](https://user-images.githubusercontent.com/23072548/148639100-864b46ee-f69f-4f9a-a695-848936050b50.gif)
_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](https://pusher.com/), [Liveblocks](https://liveblocks.io/).
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](https://user-images.githubusercontent.com/23072548/148639423-529a7027-cab9-4085-a9f4-d85e28cce744.gif)
_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](https://user-images.githubusercontent.com/23072548/148639039-f810a907-6d43-433c-b446-92d90f240281.gif)
_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](https://user-images.githubusercontent.com/23072548/148639280-a26003c6-f628-49cf-a4ea-3cf1b7881fdf.gif)
_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](https://user-images.githubusercontent.com/23072548/148640411-14821049-fbca-4d39-ae0e-fb601a6f27de.gif)
_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](https://user-images.githubusercontent.com/23072548/148639100-864b46ee-f69f-4f9a-a695-848936050b50.gif)
_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.
```ts
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.
```ts
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`.
```tsx
// hooks/usePerfectCursorimport { 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:
```tsx
// components/Cursorimport * as React from "react"
import { usePerfectCursor } from "../hooks/usePerfectCursors"export function Cursor({ point }: { point: number[] }) {
const rCursor = React.useRef(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 (
)
}
```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](https://github.com/sponsors/steveruizok?frequency=recurring&sponsor=steveruizok).
## Author
- [@steveruizok](https://twitter.com/steveruizok)