How to Calculate Velocity for Swipe-to-Dismiss Gestures: A Complete Implementation Guide

Calculate swipe velocity by dividing the absolute distance traveled by the elapsed time (px/ms), then compare against a threshold of 0.11 px/ms to determine whether to dismiss or reset the element.

Swipe-to-dismiss gestures are a cornerstone of modern mobile and web interfaces. In the emilkowalski/skills repository, velocity-based dismissal provides a natural feel by responding to quick flicks even when the physical distance is small. This article breaks down the exact calculation method used in the Emil-Design-Eng skill, including production-ready code you can adapt to your project.

Core Formula for Swipe Velocity

The velocity calculation follows a simple physics principle: speed equals distance over time. Expressed in code:

const velocity = Math.abs(swipeAmount) / timeTaken; // px/ms

Where:

  • swipeAmount — the horizontal (or vertical) distance in pixels between pointer down and pointer up
  • timeTaken — the elapsed time in milliseconds from gesture start to end

This velocity value enables dynamic dismissal decisions that feel responsive on touch devices.

Implementation in emilkowalski/skills

The repository codifies this pattern in skills/emil-design-eng/SKILL.md. The dismissal logic checks both distance and velocity thresholds:

const velocity = Math.abs(swipeAmount) / timeTaken;               // ⟶ velocity computation
if (Math.abs(swipeAmount) >= SWIPE_THRESHOLD || velocity > 0.11) {
    // dismiss the element
}
  • Distance threshold (SWIPE_THRESHOLD) — typically 150 px, the minimum drag distance to trigger dismissal
  • Velocity threshold (0.11 px/ms) — allows quick flicks to dismiss even below the pixel threshold

This dual-threshold approach is also documented in skills/find-animation-opportunities/SKILL.md, which states that a swipe qualifies as dismissal when Math.abs(distance)/elapsedMs > ~0.11[find‑animation‑opportunities/SKILL.md#L90].

Production Code Examples

Example 1: Vanilla JavaScript Implementation

A minimal, framework-agnostic approach using Pointer Events:

let startX = 0;
let startTime = 0;

function onPointerDown(e) {
  startX = e.clientX;
  startTime = performance.now();
}

function onPointerUp(e) {
  const endX = e.clientX;
  const endTime = performance.now();

  const distance = endX - startX;                // px
  const elapsed = endTime - startTime;          // ms
  const velocity = Math.abs(distance) / elapsed; // px/ms

  const SWIPE_THRESHOLD = 150; // px
  const VELOCITY_THRESHOLD = 0.11; // px/ms

  if (Math.abs(distance) >= SWIPE_THRESHOLD || velocity > VELOCITY_THRESHOLD) {
    // Dismiss the element
    console.log('Dismissed');
  } else {
    // Return to original position
    console.log('Cancelled');
  }
}

// Attach listeners
element.addEventListener('pointerdown', onPointerDown);
element.addEventListener('pointerup', onPointerUp);

Example 2: React Hook for Swipe-to-Dismiss

Encapsulate the logic in a reusable hook:

import { useRef } from 'react';

const SWIPE_THRESHOLD = 150;      // px
const VELOCITY_THRESHOLD = 0.11; // px/ms

export function useSwipeToDismiss(onDismiss: () => void) {
  const startX = useRef(0);
  const startTime = useRef(0);

  const onPointerDown = (e: React.PointerEvent) => {
    startX.current = e.clientX;
    startTime.current = e.timeStamp;
  };

  const onPointerUp = (e: React.PointerEvent) => {
    const distance = e.clientX - startX.current;
    const elapsed = e.timeStamp - startTime.current;
    const velocity = Math.abs(distance) / elapsed;

    if (Math.abs(distance) >= SWIPE_THRESHOLD || velocity > VELOCITY_THRESHOLD) {
      onDismiss();
    }
  };

  return { onPointerDown, onPointerUp };
}

Usage:

function DismissibleCard() {
  const { onPointerDown, onPointerUp } = useSwipeToDismiss(() => {
    setIsVisible(false);
  });

  return (
    <div onPointerDown={onPointerDown} onPointerUp={onPointerUp}>
      Swipe me to dismiss
    </div>
  );
}

Example 3: Reusing Repository Constants

When working within a codebase that follows the skills conventions:

import { SWIPE_THRESHOLD } from './constants.js'; // defined elsewhere
// Velocity threshold is hard‑coded as 0.11 px/ms per the skill guidelines

function shouldDismiss(swipeAmount, timeTaken) {
  const velocity = Math.abs(swipeAmount) / timeTaken;
  return Math.abs(swipeAmount) >= SWIPE_THRESHOLD || velocity > 0.11;
}

Why 0.11 px/ms?

The 0.11 px/ms threshold (equivalent to 110 px/second) balances two competing needs:

  1. Responsiveness — Quick flicks feel instant and satisfying
  2. Precision — Slow, deliberate drags can still be canceled without accidental dismissal

This value emerges from user testing and is codified across multiple skills in the repository. Adjust it based on your specific UX requirements, but test thoroughly—values above 0.15 px/ms risk accidental dismissals, while values below 0.08 px/ms feel sluggish.

Key Source Files

File Purpose
skills/emil-design-eng/SKILL.md Concrete velocity calculation and dismissal decision logic
skills/find-animation-opportunities/SKILL.md Design guidelines for velocity-based dismissal patterns
skills/review-animations/STANDARDS.md Broader standards for gesture handling and performance

Summary

  • Calculate velocity with Math.abs(distance) / elapsedMs to get px/ms
  • Use dual thresholds: a pixel distance (typically 150 px) AND velocity (0.11 px/ms)
  • Reference the source: skills/emil-design-eng/SKILL.md in emilkowalski/skills provides the canonical implementation
  • Framework flexibility: The pattern works identically in vanilla JS, React, Vue, or any pointer-event-capable environment

Frequently Asked Questions

What units should I use for swipe velocity?

Use pixels per millisecond (px/ms). This matches the repository's conventions and simplifies mental math—0.11 px/ms equals 110 pixels per second, an intuitive scale for gesture timing.

Why check both distance and velocity thresholds?

The distance threshold prevents accidental dismissal from tiny, unintended movements. The velocity threshold ensures that intentional quick flicks—common on mobile—still trigger dismissal even when the physical drag distance is short. Together they create a forgiving yet responsive interaction.

Can I adjust the 0.11 px/ms velocity threshold?

Yes, but test extensively. The 0.11 value in skills/emil-design-eng/SKILL.md and skills/find-animation-opportunities/SKILL.md represents a refined default based on real-world usage. Increase it for more deliberate gestures; decrease it for snappier response, but watch for accidental triggers.

How do I handle vertical swipe-to-dismiss?

Apply the same calculation to the Y-axis: Math.abs(endY - startY) / elapsedMs. The repository's pattern works for any directional swipe—just ensure your threshold constants match the intended axis scale.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →