Skip to main content

Maths

Everything the joystick computes is exported and unit-tested. You can import the functions, check the numbers, and drive a dial or a trackpad with the same curve.

import { speedFor, deflection, computeDelta } from '@jugaaadi/joystick';

The acceleration curve

This is the whole trick: precise near the centre, fast at the edge, no mode switch. The plot below is drawn live by calling the exported speedFor() — the same function the stick uses.

Raise accelExponent and the centre gets flatter — more travel before anything happens, so fine work is easier and the top end arrives abruptly. Lower it and the response is closer to linear.

maxSpeedMultiplier is the ceiling: how much faster the edge is than the centre.

Exported functions

All pure. No React, no DOM.

Function
deflection(x, y, radius)0–1 deflection from a thumb offset
speedFor(t, accel)the curve — deflection to speed multiplier
computeDelta(input)a full per-frame delta from pointer state
computeStep(...)one keyboard/tap step
snapRadians(rad, deg)snap an angle to a grid
normalizeAngle(rad)wrap to a single turn
mapVector(...)map a vector onto the active axes
dominantAxisOf(total)which axis moved most
resolveTap(...)decide tap vs drag
clampToRing(x, y, r)keep the thumb inside
travelRadius(size)ring radius for a given size

Constants: TRAVEL_RATIO, DEFAULT_SIZE, DEFAULT_DEADZONE, DEFAULT_ACCEL_EXPONENT, DEFAULT_MAX_SPEED_MULTIPLIER, DEFAULT_ROTATE_SNAP_DEG, DEFAULT_ACCEL.

Why it's exported

Two reasons, both practical.

It's testable. The behaviour of a joystick is otherwise something you can only feel. A change to the curve that "seems fine" is a change nobody can review. Because the maths is pure and exported, the tests pin actual numbers — and a PR that alters the feel has to say so in a diff.

It's reusable. The same curve can drive a slider, a scroll-wheel handler or a gamepad axis. You don't have to mount a joystick to get its response.

// a dial with the joystick's feel, no joystick
const multiplier = speedFor(dialDeflection, {
exponent: 2.2,
maxMultiplier: 6,
deadzone: 0.06,
});

Deadzone

deadzone is deflection ignored near the centre, as a 0–1 fraction. It exists because a thumb resting on a touchscreen is never perfectly still, and a stick that drifts while "stationary" is worse than one that needs a nudge to start.

Default 0.06. Raise it for shaky hands or rough environments; drop it to zero if you're driving from a mouse and want immediate response.