Search code examples
javascripthtmlcsscanvasmouseevent

Mouse event position on a canvas rescaled by a CSS rule


When a HTML canvas is in "standard" 1:1 scale (neither enlarged nor shrinked by a CSS rule), it is simple to get the mouse position in the canvas coordinates:

c2.onpointermove = (e) => {
    var mousemove_coords = { x: e.offsetX, y: e.offsetY };

Here is an example where the canvas' size gets modified by flex + max-width CSS rules. The cursor should change only on top of the green square, which is not the case here, the cursor changes on bottom right of the square.

How to get the coordinates of a mouse event over a canvas when it is resized by a CSS rule?

var c1 = document.getElementById("canvas1"), ctx1 = c1.getContext("2d");
var c2 = document.getElementById("canvas2"), ctx2 = c2.getContext("2d");
var w = 2000, h = 1000;
var x0 = 50, y0 = 75, x1 = 100, y1 = 200;
ctx1.canvas.width = w;
ctx1.canvas.height = h; 
ctx1.rect(0, 0, w, h);
ctx1.fill();
ctx2.canvas.width = w;
ctx2.canvas.height = h; 
ctx2.rect(x0, y0, x1, y1);
ctx2.fillStyle = "green";
ctx2.fill();
c2.onpointermove = (e) => {
    var mousemove_coords = { x: e.offsetX, y: e.offsetY };
    var hovered =
        mousemove_coords.x >= x0 &&
        mousemove_coords.x <= x1 &&
        mousemove_coords.y >= y0 &&
        mousemove_coords.y <= y1;
    c2.style.cursor = hovered ? "move" : "crosshair";
};
body, .container { height: 100%; width: 100%; margin: 0; padding: 0; }
.container { display: flex; align-items: center; justify-content: center; background-color: yellow; }
.left-column { margin: 1rem; flex: 1; display: flex; align-items: center; justify-content: center; }
.canvas-wrapper { position: relative; }
#canvas1 { max-width: 100%; max-height: 100%; }
#canvas2 { position: absolute; top: 0; left: 0; max-width: 100%; max-height: 100%; }
.right-column { width: 300px; }
<div class="container">
    <div class="left-column column">
        <div class="canvas-wrapper">
            <canvas id="canvas1"></canvas>
            <canvas id="canvas2"></canvas>
        </div>
    </div>
    <div class="right-column column">
        Hello world
    </div>
</div>

Edit: in my real code, I'm drawing on the canvas from frames received by HTTP requests with:

update_frame_task = setInterval(() => {
        fetch("get_frame")
            .then((r) => r.arrayBuffer())
            .then((arr) => {
                if (size === null) {
                    size = { w: 2000, h: 1000 };
                    ctx.canvas.width = size.w;
                    ctx.canvas.height = size.h;
                    ctx2.canvas.width = size.w;
                    ctx2.canvas.height = size.h;
                }
                var byteArray = new Uint8ClampedArray(arr);
                var imgData = new ImageData(byteArray, size.w, size.h);
                ctx.putImageData(imgData, 0, 0);
            });
    }, 200);

Solution

  • You can scale the mouse co-ordinates to the CSS resize using getComputedStyle.

    Also, canvas rect uses width and height.

    var c1 = document.getElementById("canvas1"), ctx1 = c1.getContext("2d");
    var c2 = document.getElementById("canvas2"), ctx2 = c2.getContext("2d");
    var w = 2000, h = 1000;
    var x0 = 50, y0 = 75, x1 = 100, y1 = 200;
    ctx1.canvas.width = w;
    ctx1.canvas.height = h; 
    ctx1.rect(0, 0, w, h);
    ctx1.fill();
    ctx2.canvas.width = w;
    ctx2.canvas.height = h; 
    ctx2.rect(x0, y0, x1, y1);
    ctx2.fillStyle = "green";
    ctx2.fill();
    ratio=w/parseFloat(window.getComputedStyle(c2).width);
    c2.onpointermove = (e) => {
    
        var mousemove_coords = { x: e.offsetX*ratio, y: e.offsetY*ratio };
    
        k.innerHTML=mousemove_coords.x+'<br>'+mousemove_coords.y; // scaled coords
    
        var hovered =
            mousemove_coords.x >= x0 &&
            mousemove_coords.x <= x0+x1 &&
            mousemove_coords.y >= y0 &&
            mousemove_coords.y <= y0+y1;
        c2.style.cursor = hovered ? "move" : "crosshair";
    };
    body, .container { height: 100%; width: 100%; margin: 0; padding: 0; }
    .container { display: flex; align-items: center; justify-content: center; background-color: yellow; }
    .left-column { margin: 1rem; flex: 1; display: flex; align-items: center; justify-content: center; }
    .canvas-wrapper { position: relative; }
    #canvas1 { max-width: 100%; max-height: 100%; }
    #canvas2 { position: absolute; top: 0; left: 0; max-width: 100%; max-height: 100%; }
    .right-column { width: 300px; }
    <div class="container">
        <div class="left-column column">
            <div class="canvas-wrapper">
                <canvas id="canvas1"></canvas>
                <canvas id="canvas2"></canvas>
            </div>
        </div>
        <div id='k' class="right-column column">
            Hello world
        </div>
    </div>