npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

scurry-n-slide

v1.0.1

Published

Pointer capture; choose your own abstraction.

Downloads

458

Readme

Scurry’n’slide — pointer capture; choose your own abstraction

A pointer tracker for mouse, pen, and touch. It handles pointer capture and concurrent pointers, each with its own state. The callbacks receive the PointerEvent as-is, along with the state you assigned to that pointer. The rest is up to your application — no naming conventions, patterns, or execution style — nothing is forced on you.

Since the interaction definition lives in your code, removing this library at any point in the future means replacing the pointer bookkeeping, not rewriting the interaction. The callback shape is quite common, it won’t stay in the way.

Check the demos on the project page: https://myshkin.eu/scurry-n-slide

Usage

Install the package or copy the distributable file into a project:

npm install scurry-n-slide

The package is a native ECMAScript module:

import trackPointers from "scurry-n-slide";

const ball = document.querySelector(".ball");

const cleanup = trackPointers(ball, {
    start(event) {
        if (event.button !== 0) return false;

        return {
            pointerX: event.clientX,
            pointerY: event.clientY,
            x: Number(ball.dataset.x || 0),
            y: Number(ball.dataset.y || 0),
        };
    },

    move(event, state) {
        const x = state.x + event.clientX - state.pointerX;
        const y = state.y + event.clientY - state.pointerY;
        ball.dataset.x = x;
        ball.dataset.y = y;
        ball.style.transform = `translate(${x}px, ${y}px)`;
    },

    end(event) {
        if (event?.type === "pointerup") {
            console.log("completed");
        } else {
            console.log("cancelled");
        }
    },
});

// The cleanup function could be passed to the owning component’s
// destructor. Calling it during an interaction ends every active
// pointer with an undefined event.
function destroy() {
    cleanup();
}

The cleanup function is the return value, so it drops straight into a host that already expects a teardown function:

useEffect(() => trackPointers(ref.current, { start, move, end }), []);

Configure the area that starts the interaction in CSS, before it begins:

.ball {
    touch-action: none;
    user-select: none;
}

Choose the touch-action value to match the effect you want.

API

trackPointers(element, { start, move, end }) -> cleanup

start(event)

Runs for each pointerdown that reaches element.

  • Return false to reject the pointer. The tracker will not request capture or deliver further callbacks. Browsers may still apply native implicit capture to direct inputs such as touch.
  • Any other return value is accepted as that pointer’s state and is passed unchanged to move and end.
  • The callback is synchronous. A returned promise is treated as state and is not awaited.

move(event, state)

Runs for each captured pointermove. The original PointerEvent is passed as-is.

Simultaneous pointers have independent state values. Interaction between pointers (multi-pointer gestures) could be handled in an outer scope.

end(event, state)

Runs exactly once for every accepted pointer.

  • A pointerup event is a regular completion.
  • pointercancel, lostpointercapture, and browser recovery events are cancellations. The actual DOM event is passed through.
  • When no per-pointer causal DOM event exists, such as programmatic cleanup or document adoption, end receives undefined.

cleanup()

Stops future interactions, releases active captures, and calls end once for each active pointer with an undefined event. Repeated calls do nothing.

TypeScript

Types ship with the package as a *.d.ts file. State is inferred from the return value of start.

trackPointers(ball, {
    start: (event) => ({ from: event.clientX }),
    move: (event, state) => state.from,   // state is { from: number }
});

What it leaves to you

Scurry’n’slide does not call preventDefault, stop event propagation, change styles, filter input, or suppress clicks. In particular:

  • The tracker never sets touch-action. Where it belongs depends on your layout and stacking, which the tracker cannot see.
  • Pointer capture retargets pointer events to the tracked element, so event.target is that element and not whatever sits under the pointer.

Errors

trackPointers throws only where you call it. A TypeError means the element does not support pointer capture or a handler is not a function, and installing the pointerdown listener may propagate a native error from the supplied target. All of it happens before the call returns, where the calling code can fix or handle it.

After registration succeeds, the tracker does not throw:

  • Pointer-capture, document, iframe, and listener failures cancel affected interactions through end.
  • cleanup() remains non-throwing and attempts to end every active pointer.
  • Exceptions from consumer start, move, and end callbacks are reported unchanged through the owner window’s standard reportError() mechanism. This keeps them visible to developer tools and global error monitoring without turning them into library control flow.

A throwing start does not accept its pointer. A throwing move leaves its pointer active. A pointer stops being tracked before its end runs, so a throwing end cannot leave it stuck.

Engine support

The target is current evergreen browsers with Pointer Events and pointer capture. The project doesn’t provide a mouse/touch fallback for legacy browsers.

Importing the module does not access browser globals. Calling trackPointers requires a browser element that supports pointer capture.

Vendoring

The whole project fits into dist/scurry-n-slide.js — a self-contained file generated from this documentation, LICENSE, and index.js. Copy dist/scurry-n-slide.d.ts alongside it to keep the types.

Do not edit the generated files directly. Run:

npm run build

Story

The project grew out of a gist written in August 2022 and used in various projects as a base for drag interactions. The shape settled early. This repo fixes a few inherent shortcomings and packages the result.

License

MIT