HTML5 Canvas stroke not following mouse Y point

I’m writing a drawing app in HTML5 Canvas, which includes a freehand draw with the mouse.

I have a problem whereby the mouse move to draw the stroke is not under the crosshair cursor. The X co-ordinate is fine, but the Y coordinate is offset by a varying amount as the mouse pointer is moved (closer at the top of the page, drifts further away as we closer to the bottom).

It has something to do with the ‘header bar’ div at the top.

Here is the code.

<style>
#divContainer {
    width: 100%;
    height: 100%;
}
#divHeader {
    position: absolute;
    left: 0px;
    top: 0px;
    right: 0px;
    height: 28px;
    background-color: #333;
}
#divContentArea {
    position: absolute;
    left: 0px;
    top: 29px;
    right: 0px;
    bottom: 5px;
}
#divContentCenter {
    position: absolute;
    top: 0px;
    left: 0px;
    bottom: 0px;
    right:0px;
}

.canvascontainer {
  position: relative;
  overflow: auto;
  width:100%;
  height:100%;
}

.canvas {
   cursor: crosshair;
   width: 100%;
   height: 100%;
   position:absolute;
   left:0px;
   top:0px;
   z-index: 2;
}

.maincanvas {
   cursor: crosshair;
   width: 100%;
   height: 100%;
   position:absolute;
   left:0px;
   top:0px;
   z-index: 1;
}
</style>

<div id="divContainer">

   <div id="divHeader">
    The Header
   </div>

   <div id="divContentArea">
      <div id="divContentCenter">

         <div id='canvascontainer'  class='canvascontainer' >
            <canvas id="canvas" class='canvas'>
            Sorry, your browser does not support a canvas object.
            </canvas>
            <canvas id="maincanvas" class='maincanvas'>
            Sorry, your browser does not support a canvas object.
            </canvas>
         </div>

      </div>
   </div>

</div>

<script>
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');

const canrect = canvas.getBoundingClientRect();

canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

var maincanvas = document.getElementById('maincanvas');
var maincontext = maincanvas.getContext('2d');

maincanvas.width = window.innerWidth;
maincanvas.height = window.innerHeight;

var lastPoint;
var startPoint;


var isDrawing = false;

context.lineWidth = 3;
context.lineJoin = context.lineCap = 'round';
context.setLineDash([0, 0]);
context.globalAlpha = 1.0;

function drawGuideLines() {
   
   for ( i = 0; i < canvas.height; i += 20 ) {
      context.beginPath(); 
      context.setLineDash([2, 2]);
      context.lineWidth = 1;
      if ( i % 60 == 0 ) {
         context.lineWidth = 2;
      }
      context.strokeStyle = '#ccc';
      context.moveTo(0,i);
      context.lineTo(canvas.width,i);
      context.stroke();
   }
   for ( i = 0; i < canvas.width; i += 20 ) {
      context.beginPath(); 
      context.setLineDash([2, 2]);
      context.lineWidth = 1;
      if ( i % 60 == 0 ) {
         context.lineWidth = 2;
      }
      context.strokeStyle = '#ccc';
      context.moveTo(i,0);
      context.lineTo(i, canvas.height);
      context.stroke();
   }

}

function getMousePos(e) {
    return {
      x: e.offsetX - canrect.left,
      y: e.offsetY + canrect.top
    };
}

function clearPage() {
   context.clearRect(0, 0, canvas.width, canvas.height);
}

function copyToMain () {

   maincontext.drawImage(canvas, 0, 0);
   clearPage();

}

canvas.onmousedown = function(e) {

   isDrawing = true;   
   canvas.addEventListener("mousemove", drawDirectPath, false);

   lastPoint = { x: e.clientX, y: e.clientY };
   lastPoint = { x: e.offsetX, y: e.offsetY };
//   lastPoint = { x: e.offsetX, y: e.PageY };
   lastPoint = getMousePos(e);
      
};

function drawDirectPath(e) {

   if (!isDrawing) return;

   context.beginPath();
   context.setLineDash([0, 0]);
   context.lineWidth = 3;
   context.strokeStyle = 'red';
   context.fillStyle = 'red';

   //show_mouse_info(e, 'GetMousePos:' + getMousePos(e).x + ', ' + getMousePos(e).y);
   //show_mouse_info(e, 'boundrect:' + canrect.x + ', ' + canrect.y);

   //mx = e.clientX;
   //my = e.clientY;
   mx = e.offsetX;
   my = e.offsetY;

   context.moveTo(lastPoint.x, lastPoint.y);
   context.lineTo(mx, my);
   context.stroke();
   lastPoint = { x: mx, y: my };

}


canvas.onmouseup = function() {
   isDrawing = false;
   copyToMain ();
};

canvas.onmouseleave = function() {
   isDrawing = false;
   copyToMain ();
};

drawGuideLines();

</script>

I have tried using OffsetX/Y, PageX/Y, clinetX/Y to see if these make a difference but I cannot solve the problem.

The test, click the mouse in the top right or top left and drag/draw down diagonally to the opposite bottom corner to see the effect.

Answer

Don’t give the canvas a width and height of 100% using CSS.

.canvas {
   cursor: crosshair;
   position:absolute;
   left:0px;
   top:0px;
   z-index: 2;
}

Couple other things you may want to consider

Always declare variables for (let i =...

Don’t make getBoundingClientRect() a const. The reason for this is if you needed to add a resize function you wouldn’t be able to change the bounds because the variable holds the original bounds.

You are assigning lastPoint over and over. Not really sure what this is about.

   lastPoint = { x: e.clientX, y: e.clientY };
   lastPoint = { x: e.offsetX, y: e.offsetY };
//   lastPoint = { x: e.offsetX, y: e.PageY };
   lastPoint = getMousePos(e);