<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged browser-games</title>
<link>https://coderlegion.com/tag/browser-games</link>
<description>Powered by Question2Answer</description>
<item>
<title>Your canvas clicks are in the wrong coordinate system</title>
<link>https://coderlegion.com/30627/your-canvas-clicks-are-in-the-wrong-coordinate-system</link>
<description>&lt;p&gt;A target at &lt;code&gt;(100, 60)&lt;/code&gt; is one quarter across and down a 400 × 240 game. Display the canvas at 800 × 480 CSS pixels and the same target appears at &lt;code&gt;(200, 120)&lt;/code&gt; relative to the canvas corner. A pointer handler that compares those CSS coordinates directly with the target will miss it.&lt;/p&gt;
&lt;p&gt;At the original display size, the two sets of numbers happened to match. Resizing exposed the missing conversion.&lt;/p&gt;
&lt;h3 id=&quot;follow-one-click-through-the-coordinates&quot;&gt;Follow one click through the coordinates&lt;/h3&gt;
&lt;p&gt;Place the larger canvas at viewport position &lt;code&gt;(100, 50)&lt;/code&gt;. A click on the target has client coordinates &lt;code&gt;(300, 170)&lt;/code&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Subtract the canvas origin: &lt;code&gt;300 − 100 = 200&lt;/code&gt; CSS pixels across, and &lt;code&gt;170 − 50 = 120&lt;/code&gt; down.&lt;/li&gt;
&lt;li&gt;Divide by the displayed size: &lt;code&gt;200 / 800&lt;/code&gt; and &lt;code&gt;120 / 480&lt;/code&gt; both give &lt;code&gt;0.25&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Multiply by the logical size: &lt;code&gt;0.25 × 400 = 100&lt;/code&gt; across, and &lt;code&gt;0.25 × 240 = 60&lt;/code&gt; down.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The fraction carries the position from one set of dimensions to another. If the bitmap is 1600 × 960, the same point is &lt;code&gt;(400, 240)&lt;/code&gt; in bitmap pixels. That is useful to a pixel operation, but it is still &lt;code&gt;(100, 60)&lt;/code&gt; to the game.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=3983672196422537203&quot; alt=&quot;A normalized point one quarter across and down maps to CSS coordinates 200,120, logical coordinates 100,60, and bitmap coordinates 400,240 for the listed dimensions.&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;One normalized point expressed in CSS pixels, logical game units and bitmap pixels. Original diagram made with AI-assisted code; the dimensions are chosen examples.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The helper below implements that route for a canvas whose full bitmap fills its content box. It assumes no border, padding, CSS transforms on the canvas or its ancestors, letterboxing, clipped viewport or camera transform. The event and rectangle come from the same document. Use an engine's pointer-to-world API when it already accounts for additional transforms.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function toLogical(
  clientX, clientY, rect,
  width, height
) {
  if (![clientX, clientY,
        rect.left, rect.top,
        rect.width, rect.height,
        width, height]
      .every(Number.isFinite)
      || rect.width &amp;lt;= 0
      || rect.height &amp;lt;= 0
      || width &amp;lt;= 0
      || height &amp;lt;= 0)
    return null;
  const u =
    (clientX - rect.left)
    / rect.width;
  const v =
    (clientY - rect.top)
    / rect.height;
  if (u &amp;lt; 0 || u &amp;gt;= 1
      || v &amp;lt; 0 || v &amp;gt;= 1)
    return null;
  return {
    x: u * width,
    y: v * height
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On &lt;code&gt;pointerdown&lt;/code&gt;, pass &lt;code&gt;event.clientX&lt;/code&gt;, &lt;code&gt;event.clientY&lt;/code&gt;, a fresh &lt;code&gt;canvas.getBoundingClientRect()&lt;/code&gt;, and the logical scene dimensions. Ignore &lt;code&gt;null&lt;/code&gt;; otherwise use the returned position for hit testing.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/clientX&quot;&gt;&lt;code&gt;clientX&lt;/code&gt; and &lt;code&gt;clientY&lt;/code&gt;&lt;/a&gt; and &lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect&quot;&gt;&lt;code&gt;getBoundingClientRect()&lt;/code&gt;&lt;/a&gt; share viewport coordinates. Reading the rectangle at the press accounts for scrolling. Adding a scroll offset—or substituting document-relative &lt;code&gt;pageY&lt;/code&gt;—would mix coordinate systems again.&lt;/p&gt;
&lt;p&gt;The helper includes the left and top edges and rejects the right, bottom and outside. It does not clamp a gutter click onto an edge target. Keep fractional results for logical hit testing.&lt;/p&gt;
&lt;h3 id=&quot;density-belongs-on-the-drawing-side&quot;&gt;Density belongs on the drawing side&lt;/h3&gt;
&lt;p&gt;Increasing bitmap density can sharpen the canvas without moving the target in the game. &lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio&quot;&gt;&lt;code&gt;devicePixelRatio&lt;/code&gt;&lt;/a&gt; relates physical pixels to CSS pixels and can change with zoom or a different display. Multiplying the helper's output by DPR would apply a second, unwanted conversion.&lt;/p&gt;
&lt;p&gt;For the 400 × 240 logical scene, use &lt;code&gt;ctx.setTransform(canvas.width / 400, 0, 0, canvas.height / 240, 0, 0)&lt;/code&gt; when drawing. The actual integer bitmap dimensions account for resize rounding. &lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setTransform&quot;&gt;&lt;code&gt;setTransform&lt;/code&gt;&lt;/a&gt; replaces the scale rather than compounding it on each resize.&lt;/p&gt;
&lt;p&gt;Keep CSS size independent of the bitmap attributes, for example with an explicit width and aspect ratio. Otherwise enlarging &lt;code&gt;canvas.width&lt;/code&gt; can enlarge an intrinsically sized element too. Assigning &lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/width&quot;&gt;&lt;code&gt;canvas.width&lt;/code&gt;&lt;/a&gt; resets the context, so restore the transform and drawing styles afterward. Handle density changes even when the element's CSS size stays the same.&lt;/p&gt;
&lt;h3 id=&quot;an-iframe-can-resize-correctly-while-clicks-remain-wrong&quot;&gt;An iframe can resize correctly while clicks remain wrong&lt;/h3&gt;
&lt;p&gt;Rosebud's &lt;a rel=&quot;nofollow&quot; href=&quot;https://rosebud.ai/blog/how-to-embed-your-rosebud-game&quot;&gt;November 2024 embedding guide&lt;/a&gt; describes responsive iframe sizing. That handles the outer display; the pointer conversion still belongs to the document containing the canvas. Host-page coordinates cannot be paired with an inner canvas rectangle, and &lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Same-origin_policy&quot;&gt;same-origin restrictions&lt;/a&gt; limit cross-origin access.&lt;/p&gt;
&lt;p&gt;A correctly sized embed is therefore a place to repeat the target test, not evidence that the game's input handler is correct. First verify the arithmetic with the helper above:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const wide = {
  left: 100, top: 50,
  width: 800, height: 480
};
console.log(toLogical(
  300, 170, wide, 400, 240
)); // {x: 100, y: 60}
const small = {
  ...wide,
  width: 400, height: 240
};
console.log(toLogical(
  200, 110, small, 400, 240
)); // {x: 100, y: 60}
console.log(toLogical(
  900, 170, wide, 400, 240
)); // null: right edge
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then click that same target at two display sizes in the real delivery page, scroll, and try the edges and gutter. If the arithmetic passes but those presses fail, compare the measured content area with the helper's layout assumptions—especially borders, letterboxing and transforms.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/30627/your-canvas-clicks-are-in-the-wrong-coordinate-system</guid>
<pubDate>Sat, 10 Oct 2026 19:24:16 +0000</pubDate>
</item>
</channel>
</rss>