Skip to main content

Why Your Handler Fires Twice on Mobile, and Click After Drag

Two things surprise people about pointer input on canvas. One is a real problem you have to handle. The other is already handled, and knowing that saves you a workaround you do not need.

A tap can fire three events

Konva listens to all three DOM input families — mouse, touch, and pointer — so that a handler works everywhere without you choosing in advance. It does not merge them.

On a touchscreen the browser sends a touchstart/touchend pair, a pointerdown/pointerup pair, and then synthesises mousedown/mouseup for compatibility with pages written before touch existed. Konva turns each of those into its own event. One tap produces:

Konva eventComes from
tapthe touch pair
pointerclickthe pointer pair
clickthe synthesised mouse pair

So this runs your handler twice on a phone and once on a desktop:

// Fires for `click` and again for `tap` on a touch device.
shape.on('click tap', handleSelect);

Bind one family

The simplest fix is to stop pairing events and use the pointer family, which already covers mouse, touch, and pen:

shape.on('pointerclick', handleSelect);   // once, on every device

pointerdown, pointermove and pointerup work the same way. Pointer events are on by default (Konva.pointerEventsEnabled), and there is more in Pointer Events.

If you need to support a browser without pointer events, or you are maintaining code that already pairs click tap, guard the handler instead:

let lastHandled = 0;

function handleSelect(e) {
// A synthesised mouse event follows its touch within about 300ms.
const now = Date.now();
if (now - lastHandled < 400) return;
lastHandled = now;

// ...
}

Prefer binding one family. The timer is a workaround, and it will swallow a genuine second tap from a fast user.

Click already does not fire after a drag

This one is worth knowing because the usual workaround is unnecessary.

When a drag actually starts, Konva clears the flag that allows a click to be dispatched, so dragend is not followed by click:

shape.on('dragend', () => console.log('dragged'));
shape.on('click', () => console.log('clicked'));

// Drag the shape: you get 'dragged' only.
// Press and release without moving: you get 'clicked' only.

If you are seeing a click after a drag anyway, it is usually one of these.

You are listening to mouseup, not click. mouseup is a raw pointer event and always fires, drag or not. click is the one Konva suppresses.

The pointer never moved far enough to count as a drag. Konva.dragDistance defaults to 3 pixels. A press that wanders one or two pixels — which is normal on a touchscreen — is treated as a click, and dragstart never fires either. That is intended. Raise it if your users have shaky hands on a small target:

Konva.dragDistance = 8;

You set dragDistance to 0. Then every press is a drag, which suppresses the click you wanted.

Preventing the browser's own gestures

Separately from Konva, a touch on a canvas may scroll the page or trigger a double-tap zoom. That is the browser, and it is stopped in CSS:

#container {
touch-action: none;
}

Use touch-action: pan-y instead if the page should still scroll vertically while the canvas handles horizontal drags. See Mobile Scrolling for the full picture.