fix(minimap): hit-test against clamped rect + shrink to 100x75

Two unrelated minimap issues from user feedback:

1. Click on the red viewport rect was sometimes teleporting the
   canvas instead of starting a drag.  Cause: insideRect compared
   click coords against the UNCLAMPED rectX/rectY/rectW/rectH, but
   the rendered rect uses clampedX/clampedY (which differ when the
   user pans past a world edge).  The user clicked on the visible
   red rect, but the logical rect was off-minimap → insideRect
   returned false → fell through to the teleport branch.

   Fix: compute clamped values once at the top, render and hit-test
   against the same values.  Drag now only fires when the click
   really lands inside the visible rect.

2. The 140x105 default still ate too much canvas at typical zoom.
   Drop to 100x75 (12% of world width by 2.5%, same proportions as
   the world).  Mobile breakpoint dropped to 90x68 to stay
   proportionally smaller on phones.
This commit is contained in:
davidmonterocrespo24 2026-05-14 22:53:34 +02:00
parent 218a891c6d
commit 6242b7f16b
2 changed files with 20 additions and 16 deletions

View File

@ -70,8 +70,8 @@
@media (max-width: 720px) {
/* On phones the minimap eats too much of the canvas; shrink it. */
.canvas-minimap {
width: 140px !important;
height: 105px !important;
width: 90px !important;
height: 68px !important;
bottom: 12px;
right: 12px;
}

View File

@ -28,8 +28,8 @@ import type { Component } from '../../types/components';
import type { BoardInstance } from '../../types/board';
import './CanvasMinimap.css';
const MINIMAP_W = 140;
const MINIMAP_H = 105;
const MINIMAP_W = 100;
const MINIMAP_H = 75;
const WORLD_W = 4000;
const WORLD_H = 3000;
const SCALE_X = MINIMAP_W / WORLD_W;
@ -81,6 +81,17 @@ export const CanvasMinimap: React.FC<Props> = ({
const rectW = (vp.w / zoom) * SCALE_X;
const rectH = (vp.h / zoom) * SCALE_Y;
// Clamp the rectangle to the minimap bounds so it never paints outside
// (transiently during pinch-zoom-out, and persistently when the user
// pans past a world edge). We hit-test against these clamped values
// too — otherwise clicking the visible red rect can fall outside the
// unclamped logical rect and erroneously trigger a teleport instead
// of a drag.
const clampedW = Math.min(rectW, MINIMAP_W);
const clampedH = Math.min(rectH, MINIMAP_H);
const clampedX = Math.max(0, Math.min(MINIMAP_W - clampedW, rectX));
const clampedY = Math.max(0, Math.min(MINIMAP_H - clampedH, rectY));
// Drag state. We use a ref + window listeners (rather than React's
// onMouseMove on the minimap div) so the gesture keeps working even if
// the cursor leaves the minimap during a fast pan.
@ -137,10 +148,10 @@ export const CanvasMinimap: React.FC<Props> = ({
const localX = e.clientX - rect.left;
const localY = e.clientY - rect.top;
const insideRect =
localX >= rectX &&
localX <= rectX + rectW &&
localY >= rectY &&
localY <= rectY + rectH;
localX >= clampedX &&
localX <= clampedX + clampedW &&
localY >= clampedY &&
localY <= clampedY + clampedH;
if (insideRect) {
// Drag mode — record start state, window listeners do the rest.
dragRef.current = {
@ -155,7 +166,7 @@ export const CanvasMinimap: React.FC<Props> = ({
teleportTo(localX / SCALE_X, localY / SCALE_Y);
}
},
[rectX, rectY, rectW, rectH, pan, teleportTo],
[clampedX, clampedY, clampedW, clampedH, pan, teleportTo],
);
useEffect(() => {
@ -186,13 +197,6 @@ export const CanvasMinimap: React.FC<Props> = ({
};
}, [zoom, setPan, clampPan]);
// Clamp the rendered rectangle to the minimap bounds so it never paints
// outside (happens transiently during pinch-zoom-out).
const clampedX = Math.max(0, Math.min(MINIMAP_W - rectW, rectX));
const clampedY = Math.max(0, Math.min(MINIMAP_H - rectH, rectY));
const clampedW = Math.min(rectW, MINIMAP_W);
const clampedH = Math.min(rectH, MINIMAP_H);
return (
<div
ref={minimapRef}