Skip to content

Commit 1b180df

Browse files
committed
feat: Enhance NeedleCanvas with devicePixelRatio scaling and improve click handling
1 parent cdde4ad commit 1b180df

1 file changed

Lines changed: 24 additions & 5 deletions

File tree

src/components/NeedleCanvas.tsx

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,24 @@ const NeedleCanvas = forwardRef<NeedleCanvasHandle, NeedleCanvasProps>(function
4444

4545
// ── helpers ──────────────────────────────────────────────────────────────
4646

47+
/** Returns context already scaled for the current devicePixelRatio. */
4748
function getCtx() {
48-
return canvasRef.current?.getContext("2d") ?? null;
49+
const canvas = canvasRef.current;
50+
if (!canvas) return null;
51+
return canvas.getContext("2d") ?? null;
52+
}
53+
54+
/** Scale the canvas backing store to match devicePixelRatio. */
55+
function applyDpr() {
56+
const canvas = canvasRef.current;
57+
if (!canvas) return;
58+
const dpr = window.devicePixelRatio || 1;
59+
canvas.width = Math.round(width * dpr);
60+
canvas.height = Math.round(height * dpr);
61+
const ctx = canvas.getContext("2d");
62+
if (ctx) {
63+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
64+
}
4965
}
5066

5167
function drawBackground(ctx: CanvasRenderingContext2D) {
@@ -84,6 +100,7 @@ const NeedleCanvas = forwardRef<NeedleCanvasHandle, NeedleCanvasProps>(function
84100
// ── full redraw ───────────────────────────────────────────────────────────
85101

86102
const fullRedraw = useCallback(() => {
103+
applyDpr();
87104
const ctx = getCtx();
88105
if (!ctx) return;
89106
ctx.clearRect(0, 0, width, height);
@@ -126,19 +143,21 @@ const NeedleCanvas = forwardRef<NeedleCanvasHandle, NeedleCanvasProps>(function
126143
const canvas = canvasRef.current;
127144
if (!canvas) return;
128145
const rect = canvas.getBoundingClientRect();
129-
const scaleX = width / rect.width;
130-
const scaleY = height / rect.height;
131-
onCanvasClick((e.clientX - rect.left) * scaleX, (e.clientY - rect.top) * scaleY);
146+
// coords are in CSS pixels — no DPR correction needed here because
147+
// the canvas transform maps CSS px → logical px automatically
148+
onCanvasClick(e.clientX - rect.left, e.clientY - rect.top);
132149
}
133150

134151
return (
135152
<canvas
136153
ref={canvasRef}
154+
// Physical size is set dynamically via applyDpr(); these attrs are just
155+
// initial placeholders — style controls the visible CSS size.
137156
width={width}
138157
height={height}
139158
onClick={handleClick}
140159
className="rounded-xl cursor-crosshair"
141-
style={{ display: "block" }}
160+
style={{ display: "block", width, height }}
142161
/>
143162
);
144163
});

0 commit comments

Comments
 (0)