SKU // 134

JS Web Pointer

Mouse, finger, and pen are three different ways to point at a screen, and for years each needed its own events. Pointer events unify them: one set of events that works the same for all three, with a property that tells you which one it is. This lesson covers the events, what is inside them, how to follow a drag, and how to draw.

// One Set of Events for Every Pointer

Event Fires when
"pointerdown"a button was pressed or a finger or pen touched
"pointermove"the pointer moved
"pointerup"the button, finger, or pen was released
"pointercancel"the browser took over (for example, the finger became a scroll)
"pointerenter" / "pointerleave"the pointer entered or left an element
"pointerover" / "pointerout"like enter and leave, but they also fire for child elements

You listen with addEventListener as with any event. The old mousedown events only react to a mouse, and touchstart only to fingers. A pointer event reacts to all of them.

// What Is Inside the Event

Property Holds
event.pointerType"mouse", "touch", or "pen"
event.pointerIda number identifying this pointer (each finger has its own)
event.isPrimarywhether this is the main pointer of its type
event.clientX, event.clientYposition in the viewport, in pixels
event.pressurehow hard, from 0 to 1 (0.5 for a mouse button held down)
event.button, event.buttonswhich mouse button changed, and which are down

You can build and send a pointer event yourself with new PointerEvent(...) and dispatchEvent. That makes a safe way to see exactly what a listener receives, with no mouse needed:

const box = document.createElement("div");
document.body.appendChild(box);

box.addEventListener("pointerdown", function (event) {
  console.log(event.type, event.pointerType, event.clientX, event.clientY);
});

box.dispatchEvent(new PointerEvent("pointerdown", { pointerType: "touch", clientX: 10, clientY: 20 }));
box.dispatchEvent(new PointerEvent("pointerdown", { pointerType: "pen", clientX: 30, clientY: 40 }));
pointerdown touch 10 20 pointerdown pen 30 40

// A Follow-the-Pointer Demo

This box follows wherever you move the pointer over the area. The same code works with a mouse, a finger, or a pen:

<div id="area" style="height:160px; border:1px solid gray; position:relative; touch-action:none;">
  <div id="dot" style="width:20px; height:20px; border-radius:50%; background:tomato; position:absolute; left:0; top:0;"></div>
  <p id="info" style="margin:6px;">Move a pointer over this box</p>
</div>

<script>
  const area = document.getElementById("area");
  const dot = document.getElementById("dot");
  const info = document.getElementById("info");

  area.addEventListener("pointermove", function (event) {
    const box = area.getBoundingClientRect();
    dot.style.left = (event.clientX - box.left - 10) + "px";
    dot.style.top = (event.clientY - box.top - 10) + "px";
    info.textContent = event.pointerType + " at " + Math.round(event.clientX - box.left) + ", " + Math.round(event.clientY - box.top);
  });
</script>

The style touch-action: none tells the browser not to use a finger drag on this element to scroll the page. Without it, a finger starts scrolling, the browser sends pointercancel, and your pointer stops moving.

// Dragging: Pointer Capture

A drag has three phases: down, move, up. A problem: if the pointer leaves the element while you are dragging, the element stops getting events. element.setPointerCapture(event.pointerId) fixes it: all later events for that pointer go to the element, wherever it moves, until the pointer is released:

<div id="track" style="height:60px; border:1px solid gray; position:relative; touch-action:none;">
  <div id="knob" style="width:40px; height:40px; border-radius:6px; background:steelblue; position:absolute; left:0; top:10px; cursor:grab;"></div>
</div>
<p id="out">Drag the square</p>

<script>
  const track = document.getElementById("track");
  const knob = document.getElementById("knob");
  let dragging = false;

  knob.addEventListener("pointerdown", function (event) {
    dragging = true;
    knob.setPointerCapture(event.pointerId);
  });

  knob.addEventListener("pointermove", function (event) {
    if (!dragging) return;
    const box = track.getBoundingClientRect();
    const x = Math.max(0, Math.min(event.clientX - box.left - 20, box.width - 40));
    knob.style.left = x + "px";
    document.getElementById("out").textContent = "x = " + Math.round(x);
  });

  knob.addEventListener("pointerup", function () { dragging = false; });
  knob.addEventListener("pointercancel", function () { dragging = false; });
</script>

The line that limits x uses Math.max and Math.min together to clamp a value into a range, a pattern from the Math lesson.

// Drawing with a Pointer

Put it together with a <canvas>, a rectangle you can draw on from JavaScript. While the pointer is down, each move adds a line segment. Because it uses pressure, a pen draws thicker when pressed harder:

<canvas id="pad" width="300" height="140" style="border:1px solid gray; touch-action:none;"></canvas>

<script>
  const pad = document.getElementById("pad");
  const ctx = pad.getContext("2d");
  ctx.lineCap = "round";
  let last = null;

  pad.addEventListener("pointerdown", function (event) {
    pad.setPointerCapture(event.pointerId);
    const box = pad.getBoundingClientRect();
    last = { x: event.clientX - box.left, y: event.clientY - box.top };
  });

  pad.addEventListener("pointermove", function (event) {
    if (!last) return;
    const box = pad.getBoundingClientRect();
    const now = { x: event.clientX - box.left, y: event.clientY - box.top };
    ctx.lineWidth = 2 + event.pressure * 6;
    ctx.beginPath();
    ctx.moveTo(last.x, last.y);
    ctx.lineTo(now.x, now.y);
    ctx.stroke();
    last = now;
  });

  pad.addEventListener("pointerup", function () { last = null; });
  pad.addEventListener("pointercancel", function () { last = null; });
</script>

// Common Mistakes

// Quick Check

What is the main advantage of pointer events over mouse events?

  1. They are faster
  2. One set of events works for mouse, touch, and pen
  3. They work without JavaScript
  4. They remove the need for addEventListener
Show answer
B. Pointer events react to every kind of pointer, and event.pointerType tells you which one it was.

Why does a drag need setPointerCapture?

Show answer
If the pointer moves off the element during a drag, the element stops receiving events. Capture sends all of that pointer's events to the element until it is released.

What does this log?

const el = document.createElement("div");
el.addEventListener("pointerup", e => console.log(e.pointerType));
el.dispatchEvent(new PointerEvent("pointerup", { pointerType: "pen" }));
Show answer
pen. The event carries whatever pointerType it was created with.

// Try It

  1. Click Open Playground and draw. Try it with a finger or pen if you can
  2. Change the Playground so the line color depends on event.pointerType
  3. Add a "Clear" button that wipes the canvas with ctx.clearRect(0, 0, 300, 140)
  4. Remove touch-action: none and test on a touch screen. What happens?
// Video Walkthroughs
Last updated: Oct 11, 2026, 8:16 PM ET