# How to Implement 1:1 Pointer Tracking with Velocity Calculation for Gestures

> Implement 1:1 pointer tracking with velocity calculation for gestures. Use setPointerCapture, track position history, and compute release velocity for smooth animations in your emilkowalski/skills project.

- Repository: [Emil Kowalski/skills](https://github.com/emilkowalski/skills)
- Tags: how-to-guide
- Published: 2026-08-05

---

**Use `setPointerCapture` to track pointer movement outside element bounds, maintain a rolling history of recent positions, and compute release velocity by dividing position delta by elapsed time before passing it to a spring animation.**

Smooth, physics-driven gestures require continuous 1:1 pointer tracking and accurate velocity extraction at release. The **emilkowalski/skills** repository documents a battle-tested pattern for implementing this in web applications, combining pointer capture APIs with spring-based animations to create interfaces that feel truly responsive and alive.

## Core Concepts from the Skills Repository

The implementation rests on four interconnected principles documented across the repository's skill files:

| Concept | Implementation Source |
|---|---|
| **Pointer capture** with `setPointerCapture()` to receive events beyond element bounds | [`skills/apple-design/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/apple-design/SKILL.md) |
| **Position history buffer** for velocity calculation | [`skills/apple-design/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/apple-design/SKILL.md) |
| **Spring animation with initial velocity** for momentum continuation | [`skills/apple-design/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/apple-design/SKILL.md) |
| **Canonical spring configuration** for gesture-driven motion | [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md) |

## Step-by-Step Implementation

### 1: Capture the Pointer on Gesture Start

Call `setPointerCapture()` immediately in your `pointerdown` handler. This ensures the element continues receiving `pointermove` events even if the cursor leaves its bounds—a critical requirement for 1:1 tracking that doesn't break when users drag quickly.

```javascript
element.addEventListener('pointerdown', (e) => {
  element.setPointerCapture(e.pointerId);
  // Initialize tracking state...
});

```

According to [`skills/apple-design/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/apple-design/SKILL.md), this pattern is essential for drag-to-dismiss and flick gestures where the user needs freedom of movement without the gesture terminating prematurely【apple‑design SKILL.md†L42-L47】.

### 2: Maintain a Rolling Position History

Store timestamped positions in a fixed-length buffer during `pointermove`. The repository recommends keeping approximately **5 recent samples** to balance noise reduction with responsiveness【apple‑design SKILL.md†L43-L46】.

```javascript
const HISTORY_SIZE = 5;
const history = []; // { t: timestamp, x: number, y: number }

element.addEventListener('pointermove', (e) => {
  history.push({ t: e.timeStamp, x: e.clientX, y: e.clientY });
  if (history.length > HISTORY_SIZE) history.shift();
  
  // Apply 1:1 transform...
  element.style.transform = `translate(${e.clientX}px, ${e.clientY}px)`;
});

```

### 3: Calculate Velocity at Gesture End

Compute velocity by fitting a linear slope through your history buffer. The simplest reliable method: divide the position delta between first and last samples by their time difference.

```javascript
element.addEventListener('pointerup', () => {
  const [first, last] = [history[0], history[history.length - 1]];
  const dt = (last.t - first.t) / 1000; // seconds
  const dx = last.x - first.x;
  const dy = last.y - first.y;
  
  const vx = dx / dt; // pixels per second
  const vy = dy / dt;
});

```

### 4: Launch Spring with Extracted Velocity

Pass the computed velocity to your spring animation. The repository specifies a canonical configuration for gesture-driven springs: `{ type: 'spring', duration: 0.5, bounce: 0.2 }`【STANDARDS.md†L67-L70】.

```javascript
import { animate } from 'motion';

animate(
  element,
  { x: targetX, y: targetY },
  {
    type: 'spring',
    duration: 0.5,
    bounce: 0.2,
    velocity: { x: vx, y: vy } // Momentum continuation
  }
);

```

## Complete Vanilla JavaScript Implementation

This self-contained example implements the full pipeline with the Motion library:

```javascript
import { animate } from "https://cdn.skypack.dev/motion";

const el = document.querySelector(".draggable");
let history = null;

// ---------- Gesture start ----------
el.addEventListener("pointerdown", (e) => {
  el.setPointerCapture(e.pointerId);
  history = [{ t: e.timeStamp, x: e.clientX, y: e.clientY }];
});

// ---------- Track movement ----------
el.addEventListener("pointermove", (e) => {
  if (!history) return;
  history.push({ t: e.timeStamp, x: e.clientX, y: e.clientY });
  if (history.length > 5) history.shift();

  // 1:1 pointer tracking
  el.style.transform = `translate(${e.clientX}px, ${e.clientY}px)`;
});

// ---------- Gesture end with velocity calculation ----------
el.addEventListener("pointerup", (e) => {
  if (!history) return;
  
  const [prev, last] = [history[0], history[history.length - 1]];
  const dt = (last.t - prev.t) / 1000;
  const dx = last.x - prev.x;
  const dy = last.y - prev.y;
  const vx = dx / dt;
  const vy = dy / dt;

  // Spring with momentum continuation
  animate(
    el,
    { x: 0, y: 0 }, // Snap-back target
    {
      type: "spring",
      bounce: 0.2,
      duration: 0.5,
      velocity: { x: vx, y: vy },
    }
  );
  
  history = null;
});

```

## React Hook Implementation

For React applications, encapsulate the pattern in a reusable hook with Framer Motion:

```tsx
import { useRef } from "react";
import { motion, useMotionValue, animate } from "framer-motion";

export function usePointerGesture() {
  const x = useMotionValue(0);
  const y = useMotionValue(0);
  const history = useRef<Array<{t:number;x:number;y:number}>>([]);

  const start = (e: React.PointerEvent) => {
    (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
    history.current = [{ t: e.timeStamp, x: e.clientX, y: e.clientY }];
  };

  const move = (e: React.PointerEvent) => {
    history.current.push({ t: e.timeStamp, x: e.clientX, y: e.clientY });
    if (history.current.length > 5) history.current.shift();
    x.set(e.clientX);
    y.set(e.clientY);
  };

  const end = () => {
    const h = history.current;
    if (h.length < 2) return;
    
    const dt = (h[h.length - 1].t - h[0].t) / 1000;
    const vx = (h[h.length - 1].x - h[0].x) / dt;
    const vy = (h[h.length - 1].y - h[0].y) / dt;

    animate(y, 0, {
      type: "spring",
      bounce: 0.2,
      duration: 0.5,
      velocity: vy,
    });
  };

  return { x, y, start, move, end };
}

export default function DraggableBox() {
  const { x, y, start, move, end } = usePointerGesture();
  
  return (
    <motion.div
      style={{ x, y, touchAction: "none", position: "absolute" }}
      onPointerDown={start}
      onPointerMove={move}
      onPointerUp={end}
      onPointerCancel={end}
    />
  );
}

```

## Why Velocity-Preserving Springs Matter

The repository emphasizes **interruptibility** as a core principle for gesture-driven interfaces【apple‑design SKILL.md†L33-L35】. When a spring animation receives the release velocity:

- **No jarring stops** — motion continues naturally from the finger's speed
- **Reversible gestures** — users can interrupt and reverse direction smoothly
- **iOS-native feel** — matches "additive animations" in Apple's interface patterns【apple‑design SKILL.md†L62-L66】

## Key Source Files

| File | Purpose |
|---|---|
| [`skills/apple-design/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/apple-design/SKILL.md) | Pointer capture, velocity history, spring integration |
| [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md) | Canonical spring configs: `duration: 0.5, bounce: 0.2` |
| [`skills/animation-vocabulary/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/animation-vocabulary/SKILL.md) | Spring terminology: stiffness, damping, critical damping, momentum scenarios |
| [`skills/pick-ui-library/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/pick-ui-library/SKILL.md) | Motion/Framer Motion library recommendations |

## Summary

- **Capture** with `setPointerCapture()` to track beyond element bounds
- **Buffer** the last 5 positions with timestamps during movement
- **Calculate** velocity as Δposition/Δtime from history samples
- **Animate** with springs using the repository's standard config: `duration: 0.5, bounce: 0.2`
- **Preserve** momentum by passing computed velocity to the spring's `velocity` option

## Frequently Asked Questions

### How large should the position history buffer be?

Five samples provides optimal balance. Fewer samples amplify noise from input jitter; more samples add latency and may capture outdated momentum. The [`skills/apple-design/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/apple-design/SKILL.md) documentation specifically references this approximate window for velocity calculation【apple‑design SKILL.md†L43-L46】.

### Can I use requestAnimationFrame instead of pointer events for tracking?

Pointer events via `setPointerCapture` are required for reliable 1:1 tracking that continues outside element bounds. `requestAnimationFrame` polling loses pointer lock and cannot capture movement beyond the original element's geometry. The repository explicitly recommends pointer capture APIs for this reason【apple‑design SKILL.md†L42-L47】.

### What spring parameters work best for heavy vs. light elements?

Use the canonical config (`bounce: 0.2, duration: 0.5`) as your baseline. Adjust `bounce` higher (0.3-0.4) for lighter feel, lower (0.0-0.1) for heavier, more substantial motion. The [`skills/animation-vocabulary/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/animation-vocabulary/SKILL.md) file defines spring taxonomy for tuning these values to specific momentum scenarios【animation‑vocabulary SKILL.md†L126-L131】.

### How do I handle pointercancel events?

Treat `pointercancel` identically to `pointerup`: compute velocity from available history and launch the spring. This ensures the gesture completes gracefully if the browser interrupts the pointer stream (system gesture, touch-action conflict, etc.).