Skip to content

Fix erratic mouse behavior in the swipe (slider) compare view#56

Merged
jeremyfelt merged 1 commit into
trunkfrom
fix/swipe-slider-pointer
Jun 26, 2026
Merged

Fix erratic mouse behavior in the swipe (slider) compare view#56
jeremyfelt merged 1 commit into
trunkfrom
fix/swipe-slider-pointer

Conversation

@jeremyfelt

Copy link
Copy Markdown
Member

Symptom

In the swipe (slider) compare view, dragging the divider with a mouse feels erratic — the divider can jump, lag, or start following the cursor with no button held down.

Cause

Two classic before/after-slider pointer bugs in buildSwipe:

  1. Native image drag hijacks the gesture. The screenshots are normal <img> elements (draggable by default), and pointerdown never called preventDefault() — so mousedown-then-move starts a native image drag (ghost image, "no-drop" cursor) instead of moving the divider.
  2. The drag gets stuck on. The native drag fires pointercancel, not pointerup, and only pointerup was handled — so dragging never reset to false, and afterwards the divider tracks the cursor on plain hover.

Fix

The standard guards used by image-comparison sliders (img-comparison-slider, Juxtapose, cocoen):

  • preventDefault() on pointerdown; respond to the primary button only.
  • End the drag on pointerup and pointercancel / lostpointercapture, and release the pointer capture — an interrupted gesture can no longer get stuck.
  • Make the images non-draggable and non-interactive: draggable="false", pointer-events: none, user-select / -webkit-user-drag: none. The stage div is now the only pointer target, so a native image drag can't start.

Test plan

  • npm run lint — clean
  • npm test — 111 pass
  • Driven with a real mouse in Chromium: the divider tracks a drag accurately (to ~25%), stops on release (hover afterwards does not move it), a synthetic pointercancel mid-gesture leaves nothing stuck, images report draggable=false / pointer-events:none, and there are no console errors.

🤖 Generated with Claude Code

The before/after slider had two classic pointer bugs:

- The screenshots are normal <img> elements, draggable by default, and
  pointerdown never called preventDefault. Mousedown-then-move started a
  native image drag instead of moving the divider.
- That native drag fires pointercancel, not pointerup, and only pointerup
  was handled — so `dragging` never reset and the divider then tracked the
  cursor on plain hover with no button held.

Apply the standard guards used by image-comparison sliders:

- preventDefault on pointerdown; respond to the primary button only.
- End the drag on pointerup AND pointercancel/lostpointercapture, and
  release the pointer capture, so an interrupted gesture can't get stuck.
- Make the images non-draggable and non-interactive (draggable="false",
  pointer-events:none, user-select/-webkit-user-drag:none) so the stage is
  always the pointer target and can't be hijacked by a native image drag.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@jeremyfelt jeremyfelt merged commit 37c2b06 into trunk Jun 26, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant