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

click-pos

v0.1.2

Published

A tiny TypeScript library to easily and precisely get x/y coordinates from click or touch events, relative to window, document, or any HTML element.

Downloads

21

Readme

click-pos

npm version license install size bundlephobia npm downloads CI Coverage Status code style: prettier


Installation

Install via npm:

npm install click-pos

Basic Usage

import { getClickPos } from "click-pos";

// Example: pointer event handler (recommended for full compatibility)
document.addEventListener("pointerdown", (event) => {
  const pos = getClickPos(event, { relativeTo: "window" });
  console.log(pos.x, pos.y); // Coordinates relative to the viewport
});

API

getClickPos(event, options?)

Returns the x/y coordinates of a click or touch event, relative to the window, document, or a specific HTML element.

Parameters

  • event: MouseEvent | TouchEvent | PointerEvent
    The event object from a click, touch, or pointer handler. Only the first touch is considered for multi-touch events.

  • options?: GetClickPosOptions

    • relativeTo?: 'window' | 'document' | HTMLElement
      Reference for the coordinates:
      • 'window' (default): relative to the viewport (ignores scroll)
      • 'document': relative to the full page (includes scroll offset)
      • HTMLElement: relative to the top-left corner of the given element
    • normalizeScroll?: boolean
      If true, subtracts the current scroll offset from the result (useful for fixed elements). Only applies when relativeTo is 'window' or an HTMLElement.
    • percent?: boolean
      If true, returns coordinates as a value between 0 and 1 (percentage relative to the reference: element, window, or document).
    • filter?: (event) => boolean
      Optional callback to filter/validate the event. If it returns false, the function returns null (for getClickPos) or an empty array (for getTouchPositions).

Returns

  • { x: number, y: number }
    • x: The horizontal coordinate, in pixels (or percent if percent: true)
    • y: The vertical coordinate, in pixels (or percent if percent: true)

Example: Relative to an element

const element = document.getElementById("my-box");
element?.addEventListener("click", (event) => {
  const pos = getClickPos(event, { relativeTo: element });
  console.log(pos); // { x, y } relative to the element
});

Robustness

  • Handles both MouseEvent and TouchEvent (uses only the first touch).
  • If no valid target is provided, falls back to window coordinates.
  • Handles scroll and bounding rect calculations precisely.

Example: Filter only left mouse button

const pos = getClickPos(event, {
  filter: (e) => !(e instanceof MouseEvent) || e.button === 0,
});
if (pos) {
  // Only left mouse button events are processed
}

Example: Filter only primary pointer

const touches = getTouchPositions(event, {
  filter: (e) => !(e instanceof PointerEvent) || e.isPrimary,
});

Example: Relative to SVG

const svg = document.getElementById("mysvg");
svg?.addEventListener("pointerdown", (event) => {
  const pos = getClickPos(event, { relativeTo: svg });
  // pos.x, pos.y are in SVG user coordinates
});

Example: Percent coordinates for responsive drawing

const pos = getClickPos(event, { relativeTo: element, percent: true });
// pos.x, pos.y are between 0 and 1, regardless of element size

Example: Pinch/Zoom gesture detection

import { getTouchPositions, distance } from "click-pos";

let lastDist = null;

document.addEventListener("touchmove", (event) => {
  const touches = getTouchPositions(event, { relativeTo: "element" });
  if (touches.length === 2) {
    const d = distance(touches[0], touches[1]);
    if (lastDist !== null) {
      if (d > lastDist) {
        console.log("Zooming out");
      } else {
        console.log("Zooming in");
      }
    }
    lastDist = d;
  }
});

Example: SSR/Universal usage (Next.js, Astro, etc.)

import { getClickPos } from "click-pos";

if (typeof window !== "undefined") {
  document.addEventListener("pointerdown", (event) => {
    const pos = getClickPos(event);
    // ...
  });
}

Example: React integration

import { getClickPos } from "click-pos";

function MyComponent() {
  const ref = useRef(null);
  const handlePointerDown = (event) => {
    const pos = getClickPos(event, { relativeTo: ref.current });
    // ...
  };
  return (
    <div ref={ref} onPointerDown={handlePointerDown}>
      Click me
    </div>
  );
}

Multi-touch & Advanced Pointer Support

getTouchPositions(event, options?)

Returns an array of all active touch, pointer, or mouse positions for the given event.

  • For TouchEvent: returns all active touches (multi-touch)
  • For PointerEvent: returns the current pointer (future-proof for multi-pointer)
  • For MouseEvent: returns a single position
  • If percent: true, all positions are in percent (0-1) relative to the reference.

Type

export interface TouchPos {
  x: number;
  y: number;
  id: number | string; // touch.identifier, pointerId, or 0 for mouse
  type: "touch" | "pointer" | "mouse";
}

Example: Multi-touch

import { getTouchPositions } from "click-pos";

document.addEventListener("touchmove", (event) => {
  const touches = getTouchPositions(event, {
    relativeTo: "element",
    normalizeScroll: false,
  });
  touches.forEach((touch) => {
    console.log(`Touch ${touch.id}: x=${touch.x}, y=${touch.y}`);
  });
});

Use cases

  • Multi-finger gestures (pinch, zoom, rotate)
  • Drawing/painting apps
  • Advanced pointer/stylus support

Geometry Utilities

distance(a, b)

Returns the Euclidean distance between two points {x, y}.

import { distance } from "click-pos";
distance({ x: 0, y: 0 }, { x: 3, y: 4 }); // 5

angle(a, b)

Returns the angle (in radians) from point a to point b (0 = right, counterclockwise positive).

import { angle } from "click-pos";
angle({ x: 0, y: 0 }, { x: 0, y: 1 }); // ≈ Math.PI/2

boundingBox(points)

Returns the bounding box { minX, minY, maxX, maxY } for an array of points.

import { boundingBox } from "click-pos";
boundingBox([
  { x: 1, y: 2 },
  { x: 3, y: 4 },
]); // { minX: 1, minY: 2, maxX: 3, maxY: 4 }

Edge Cases & Robustness

  • Hidden elements: If the target element is hidden (display: none) or detached from the DOM, the function returns null (coordinates cannot be computed).
  • Nested scroll: When using normalizeScroll, the scroll offset of all scrollable parent elements is taken into account, not just the window.
  • iframe: Coordinates are always relative to the current window/document. For cross-frame calculations, you must handle coordinate translation manually (due to browser security restrictions).

SSR & Non-DOM Environments

  • The library is safe to import and use in Node.js, SSR, and non-browser environments.
  • If called outside the browser (no window/document), all functions return null or an empty array, never throwing errors.
  • This makes it safe for universal/isomorphic code and frameworks like Next.js, Nuxt, Astro, etc.

© 2024 Vincenzo Maritato — MIT License

Framework Integration Examples

React

import { getClickPos } from "click-pos";
import { useRef } from "react";

function MyComponent() {
  const ref = useRef(null);
  const handlePointerDown = (event) => {
    const pos = getClickPos(event, { relativeTo: ref.current });
    // ...
  };
  return (
    <div ref={ref} onPointerDown={handlePointerDown}>
      Click me
    </div>
  );
}

Vue 3

<template>
  <div ref="box" @pointerdown="onPointerDown">Click me</div>
</template>

<script setup>
import { ref } from "vue";
import { getClickPos } from "click-pos";
const box = ref(null);
function onPointerDown(event) {
  const pos = getClickPos(event, { relativeTo: box.value });
  // ...
}
</script>

Svelte

<script>
  import { getClickPos } from 'click-pos';
  let box;
  function handlePointerDown(event) {
    const pos = getClickPos(event, { relativeTo: box });
    // ...
  }
</script>

<div bind:this={box} on:pointerdown={handlePointerDown}>
  Click me
</div>

Angular

// my.component.ts
import { Component, ElementRef, ViewChild } from "@angular/core";
import { getClickPos } from "click-pos";

@Component({
  selector: "my-box",
  template: `<div #box (pointerdown)="onPointerDown($event)">Click me</div>`,
})
export class MyBoxComponent {
  @ViewChild("box") box!: ElementRef;
  onPointerDown(event: PointerEvent) {
    const pos = getClickPos(event, { relativeTo: this.box.nativeElement });
    // ...
  }
}