Subtract the canvas origin and scale the point
Start with the event's clientX and clientY and the rectangle returned by canvas.getBoundingClientRect(). The rectangle is positioned relative to the viewport, so subtract its left and top before scaling. If the logical game is 800 by 450 and the displayed rectangle is 400 by 225, multiply the local x and y by two. A pointer at the displayed center then maps to logical (400, 225), regardless of the page's scroll position.
Use the actual content rectangle, not assumptions based on window width. Account for CSS borders or padding outside the canvas, and refresh the mapping after a resize or orientation change. If the game uses a camera offset, letterboxing, or another world transform, invert those operations in reverse order before comparing the point with a game's hit shapes.
Test the whole surface, including dead space
Place a temporary crosshair at the converted pointer location and test all four canvas corners, the center, and each edge of a target. A simple calibration check is to click the center of a drawn circle and confirm the reported world point is near the circle's center. Then click just outside it to check that the hit boundary matches the intended forgiving margin. MDN documents clientX as viewport-relative and getBoundingClientRect() as returning viewport-relative element bounds; pairing those measurements avoids mixing coordinate spaces.
Keep rendering and hit regions linked
Store a target's world position and dimensions once, then use that data both to draw it and to test the pointer against it. Avoid maintaining separate hand-entered screen coordinates for the visible icon and click region. Retest after changing canvas scale, CSS size, device pixel ratio, or camera offset. If the coordinate probe is correct but a target still misses, inspect its shape math and input ordering instead of adding arbitrary pixel offsets.
Questions about canvas hit testing coordinates
Should I use offsetX and offsetY for canvas hits?
They can be convenient for a simple untransformed canvas, but explicit client coordinates, element bounds, and your game transform make each conversion step clearer when layout or camera scaling changes.
Does devicePixelRatio belong in pointer conversion?
Only if your pointer point and hit shapes use backing-store pixels. A common approach maps viewport coordinates into logical game units, keeping device-pixel scaling inside the rendering transform.