javascripthtmlcsscanvasmouse-position

Why does it draw far away from the cursor on a canvas?


I'll first give my code:

document.documentElement.style.backgroundImage='url(\'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20"><rect width="20" height="20" style="fill-opacity:0;stroke-width:1;stroke:rgb(0,0,255)"/></svg>\')';
var c=document.getElementById("paper").getContext("2d");
c.fillStyle="#000000";
document.documentElement.onmousemove=function(e){
    if((e.buttons||e.which)===1){
        var x=e.pageX,y=e.pageY;
        c.fillRect(x,y,1,1);
    }
};
html{width:100vw;height:100vh;overflow:hidden;background-repeat:repeat;cursor:crosshair}
body{margin:0}
#paper{position:absolute;left:0;top:0;width:100vw;height:100vh;z-index:1}
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8"/>
        <title>Graph Paper</title>
    </head>
    <body>
        <canvas id="paper"></canvas>
    </body>
</html>

I'm trying to make a virtual graph paper you can draw on with the cursor. The problem is that if you run it and (using your mouse) draw something, you will notice that the place it draws isn't actually where you're at - it's farther away from you as you get farther away from (0,0).

My code is a bit simple so I have no idea on why it is doing this. For example, if you start in the middle, and move towards (0,0), you will see it starts behind you but reaches at the same time as you. And if you start in the middle and go towards the bottom right, it will get much farther than you by the time you reach.

I suspect this has something to do with either the canvas being mispositioned or e.pageX and e.pageY to be incorrect in some way.

Why is this seemingly simple code not working?


Solution

  • Stretching a canvas with css does not change the underlying resolution of the canvas, but simply takes the resulting image, and scales it (usually rather poorly as well).

    If the canvas is e.g. stretched by a factor of two due to css, your pixel counting is accordingly off by that factor: the canvas renders it inwards as many pixels as your mouse coordinates, but the result is then stretched by css afterwards.

    You could compensate for this by some calculations, but it's often better to make the canvas itself the correct size:

    const canvas = document.getElementById("paper");
    const ctx = canvas.getContext("2d");
    document.documentElement.style.backgroundImage = 'url(\'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20"><rect width="20" height="20" style="fill-opacity:0;stroke-width:1;stroke:rgb(0,0,255)"/></svg>\')';
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    ctx.fillStyle = "#000000";
    document.documentElement.onmousemove=function(e){
        if ((e.buttons || e.which) === 1) {
            const x = e.pageX, y = e.pageY;
            ctx.fillRect(x, y, 1, 1);
        }
    };
    html {
      width: 100vw;
      height: 100vh;
      overflow: hidden;
      background-repeat: repeat;
      cursor: crosshair
    }
    body { margin: 0 }
    #paper {
      position: absolute;
      left: 0;
      top: 0;
      z-index: 1
    }
    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="UTF-8"/>
            <title>Graph Paper</title>
        </head>
        <body>
            <canvas id="paper"></canvas>
        </body>
    </html>

    Note, that this way, you don't get resizing for free. Should the window be resized, you'd need to change the canvas again, from code, which i have omitted here.