Skip to content

Click to zoom broken on Magic Mouse (and likely other devices with noisy pointer events) #56

@frontedbe

Description

@frontedbe

Bug description
Clicking an image to zoom does not work when using a Magic Mouse (and likely other devices that emit pointermove events during an otherwise stationary click). Experienced it with 2 different Magic Mouses, but my Macbook Pro trackpad clicks worked fine.

Root cause
In src/components/image.svelte, hasDragged is set based on the number of pointermove events rather than the actual pixel distance traveled:

// line 221
hasDragged = dragPositions.push({ x, y }) > 2

The Magic Mouse fires multiple pointermove events even during a completely stationary click, causing hasDragged to become true and preventing the zoom toggle in onPointerUp:

} else if (!opts.onImageClick?.(container.el, activeItem)) {
    changeZoom($zoomed ? -maxZoom : maxZoom, e) // never reached
}

To reproduce

  1. Open a BiggerPicture lightbox on a device with a Magic Mouse (or any mouse that emits pointermove during click)
  2. Click the image to zoom
  3. Nothing happens — zoom is not triggered
    Debug evidence
    Console output for a single stationary click on Magic Mouse:
⬇ pointerdown  x: 1664.61  y: 839.53
↔ pointermove  x: 1664.48  y: 839.53   // distance: 0.13px
↔ pointermove  x: 1664.38  y: 839.53   // distance: 0.23px
↔ pointermove  x: 1664.23  y: 839.53   // distance: 0.38px
↔ pointermove  x: 1664.09  y: 839.53   // distance: 0.52px
⬆ pointerup    x: 1664.09  y: 839.53

Total movement: 0.52px — clearly a click, not a drag. Yet hasDragged becomes true after 3+ pointermove events.
Expected behavior
hasDragged should be determined by pixel distance rather than event count. A small threshold (e.g. 10px) would correctly distinguish clicks from drags regardless of how many pointermove events the device emits.

Suggested fix

Replace the event-count check with a distance check:

// Instead of:
hasDragged = dragPositions.push({ x, y }) > 2
// Use:
const dist = Math.hypot(e.clientX - dragStartX, e.clientY - dragStartY)
hasDragged = dist > 10

Workaround

Until this is fixed, it's possible to intercept pointermove events in the capture phase and suppress those within a small radius:

const CLICK_THRESHOLD_PX = 10
let startX = 0, startY = 0
const capturePointerDown = (e) => { startX = e.clientX; startY = e.clientY }
const suppressTinyMoves = (e) => {
    if (Math.hypot(e.clientX - startX, e.clientY - startY) < CLICK_THRESHOLD_PX) {
        e.stopPropagation()
    }
}
document.addEventListener('pointerdown', capturePointerDown, { capture: true })
document.addEventListener('pointermove', suppressTinyMoves, { capture: true })
bp.open({
    // ...
    onClose: () => {
        document.removeEventListener('pointerdown', capturePointerDown, { capture: true })
        document.removeEventListener('pointermove', suppressTinyMoves, { capture: true })
    }
})

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions