A pointer tracker for the browser. GitHub
Scurry’n’slide
It lets you add pointer-driven interactions to your project without forcing someone else’s conventions on you. It uses the Pointer Events API to track each pointer and maintain its state. That is the smallest useful set of functionality you need to get started, and it is enough for a long while. It provides no helpers and introduces no layout tricks. It only tracks pointers, leaving the implementation to you, since you’re the one best equipped to decide how it should work.
To define an interaction, you give Scurry’n’slide the element where it starts.
Along with the element, you provide three callbacks for the pointer lifecycle: start, move, and end.
Those three names are the only naming convention this library forces on you.
Each callback receives the event that triggered it as its first argument.
The start callback lets you reject a pointer by returning false.
Any other return value becomes that pointer’s state and is passed to move and end as their second argument.
Scurry’n’slide makes sure the states of simultaneous pointers don’t collide.
That’s it. The rest is up to you. What goes in the state and what happens to the element are not Scurry’n’slide’s concern.
Let’s look at the basic example.
Basic example
A ball you can drag on a plane.
You start by importing Scurry’n’slide and getting the element. Then you call the function exported by the library, passing it the ball and an object that defines the callbacks.
In start, we return false unless it’s the primary button. We don’t need to engage on right-click.
For an accepted pointer, the state contains the initial pointer position and the element’s stored position.
Keeping the pointer’s initial position preserves its offset from the ball, so the ball doesn’t jump when grabbed.
We also need the element’s position. Since CSS applies it, we store it on the element itself rather than parsing its transform later.
In move, we use state to calculate the new position.
We apply the delta, how much the pointer has moved, to the ball’s initial position.
Then we store the new position on the element and update the CSS.
import trackPointers from "./scurry-n-slide.js";
const ball = document.querySelector(".ball");
trackPointers(ball, {
start(event) {
// reject secondary buttons
if (event.button !== 0) return false;
// the object returned by `start` becomes this pointer’s state
return {
// initial pointer position
pointerX: event.clientX,
pointerY: event.clientY,
// initial object position
x: Number(ball.dataset.x || 0),
y: Number(ball.dataset.y || 0),
};
},
move(event, state) {
// calculate from the pointer’s and object’s initial positions
const x = state.x + event.clientX - state.pointerX;
const y = state.y + event.clientY - state.pointerY;
// serialize new position
ball.dataset.x = x;
ball.dataset.y = y;
// apply new position
ball.style.transform = `translate(${x}px, ${y}px)`;
}
});It doesn’t have to be basic
That small footprint is enough to implement whatever you need. You don’t have to fight an API you didn’t design. Nor will you need to migrate when a library moves on to the next neat way to generalize the problem.
Track every pointer you can get your hands on
On desktop, it’s usually simple: every pointing device controls the same cursor, and you normally have one active pointer at a time. On touchscreens, you never know how many simultaneous contacts a device will report. By assigning distinct state to each pointer, you can be sure they won’t fight.
Mouse, pen, or as many fingers as your screen can report
I couldn’t drag less
I’ve used many drag’n’drop libraries over the years. I’ve also tried to write them from scratch. And no, don’t use the HTML Drag and Drop API; it’s not what it seems. In both cases, I struggled a lot and wished I could just get back to the functionality I was working on before drag’n’drop got in the way.
The built-in browser APIs can be confusing. Once you figure out what you actually need, it becomes tedious to build the same harness each time.
The libraries are all unique, but the friction is always there. Some are high-level, hiding the implementation behind names ending in “-able”. They allow deviations through configuration, but otherwise you’d better play along. Others are less intrusive and leave the wiring to you, instead providing ready-made modules for effects such as axis locking, animation, and sorting. That seems helpful at first, but over time they grow to cover more and more drag’n’drop-adjacent functionality. With each major release, they neatly combine new functionality with the old functionality under a kind of lore. If you upgrade, you can end up needing the new functionality while changes to old behavior are disruptive enough to make you hesitate. Finally, there are the React drag’n’drop libraries.
Ultimately, the libraries are so focused on being cohesive systems that they become foreign to the projects they enter. As if the thing you needed most was another language in your application. One wonders whether many library maintainers took the Tower of Babel as something to strive for.
Any metaphor can be simultaneously over-engineered for one use case and primitive for another. With Scurry’n’slide, I tried not to force any naming on you. It provides no drag’n’drop helpers, gestures, effects, or snapping rules. It reliably tracks pointers without hiding what happened or deciding what should happen next. Every interaction on this page is application code built on the same tiny lifecycle.
Open source
Scurry’n’slide is a small, framework-independent ECMAScript module for current browsers with Pointer Events and pointer capture. Find the source, documentation, and tests on GitHub.