0

This drawing application worked perfectly on my laptop. Testing on different screen sizes, the line drawn does not align with the cursor. I think that I will have to apply some scaling mechanism.

// DRAWING FUNCTIONALITY
var canvas, ctx, painting = false,
    previousMousePos;

  function getMousePos(canvas, evt) {
    var rect = canvas.getBoundingClientRect();
    return {
      x: evt.clientX - rect.left,
      y: evt.clientY - rect.top
    }
  };
  // Sender drawing function.
  function drawLineImmed(x1, y1, x2, y2) {
    ctx.beginPath();
    ctx.moveTo(x1, y1);
    ctx.lineTo(x2, y2);
    ctx.strokeStyle = 'white';
    ctx.stroke();
  };
  // Receiver drawing function.
  function drawLineTwo(data) {
    ctx.beginPath();
    ctx.moveTo(data.px, data.py);
    ctx.lineTo(data.mx, data.my);
    ctx.strokeStyle = 'white';
    ctx.stroke();
  };
  // Get draw data. Pass to receiver drawing function.
  socket.on('draw', function(data) {
    drawLineTwo(data);
  });
  // Sender emit drawing data.
  function mouseMove(evt) {
    var mousePos = getMousePos(canvas, evt);
    if (painting) {
      drawLineImmed(previousMousePos.x, previousMousePos.y, mousePos.x, mousePos.y);
      socket.emit('draw', {px:previousMousePos.x, py:previousMousePos.y, mx:mousePos.x, my:mousePos.y}, page);
      previousMousePos = mousePos;
    };
  };
  function clicked(evt) {
    previousMousePos = getMousePos(canvas, evt);
    painting = true;
  };
  function release(evt) {
    painting = false;
  };
  function leave(evt) {
    painting = false;
  };
  $(document).ready(function() {
    canvas = document.getElementById('canvas');
    ctx = canvas.getContext('2d');
    painting = false;
    canvas.addEventListener('mousemove', mouseMove, false);
    canvas.addEventListener('mousedown', clicked);
    canvas.addEventListener('mouseup', release);
    canvas.addEventListener('mouseleave', leave);
  });

// CSS

#canvas {
  border-radius: 2px;
  background-color: rgb(33,37,43);
  position: fixed;
  left: 1.7%;
  top: 3%;
  border-radius: 8px;
  border-style: solid;
  border-width: 3px;
  border-color: black;
  width: 80%;
}

What has to scale relative to what?

2 Answers2

1

Canvas display size is set via the style attributes and is not the same as the canvas resolution. Your code does not show that you are setting the canvas resolution to match the display size, this will cause problems when you change aspect and size.

When you get the canvas also get the bounding box and set its resolution to match the display pixel size.

var rect = canvas.getBoundingClientRect();
canvas.width = rect.width;
canvas.height = rect.height;

For more info see this answer to a related question Fullscreen canvas

Community
  • 1
  • 1
Blindman67
  • 51,134
  • 11
  • 73
  • 136
  • I didn't know to match canvas resolution with display size. I edited my code to try to match your suggestions. I am finding that a small line is tracking perfectly in line with my cursor. There is a new problem though. When the cursor moves over the canvas, any lines drawn are deleted. The line behind my cursor is deleted right after it is drawn, I can only see about one centimetre of line trailing behind. The lines drawn are sent to my client using socket.io perfectly, but when that user mouses over his canvas, the lines are deleted. Do you have any ideas? –  Sep 12 '16 at 04:14
  • 1
    The lines are deleted anytime you set the size. If you don't want them deleted you'll need to record all the lines and re-draw them after you've changed the size. – gman Sep 12 '16 at 04:17
  • Is it possible to set canvas width and height somewhere else besides getMousePos()? That way it wouldn't reset every time I move my mouse. Setting them with my canvas event listeners, for example, doesn't work. The line draws nicely but it doesn't follow my cursor. Shouldn't I only have to set this once per page load? –  Sep 12 '16 at 04:27
  • You can set the size at page load and then listen to the window.resize event and change the size in that event. For a drawing app you should use the main canvas as a display object only and create a separate canvas that holds the drawing. Thus a resize will not affect the drawing (as long as you don't change its size). Canvas has the power to create many layers and you just render them as you need. Think of the display canvas as the view port to the content you have under the hood, it is completely independent of the content. – Blindman67 Sep 12 '16 at 05:53
  • @manbearpig1 I forgot to put your handle on the last comment. – Blindman67 Sep 12 '16 at 06:21
0

As Blindman67 points out canvases have 2 sizes. The size that defines the number of pixels that are in the canvas (canvas.width & canvas.height) and the size the canvas is displayed which is set by CSS.

The normal way I resize is like this

function resizeCanvasToMatchDisplaySize(canvas) {

  // look up the size the canvas is displayed
  var desiredWidth = canvas.clientWidth;
  var desiredHeight = canvas.clientHeight;

  // if the number of pixels in the canvas doesn't match
  // update the canvas's content size.
  if (canvas.width != desiredWidth || canvas.height != desiredHeight) {
    canvas.width = desiredWidth;
    canvas.height = desiredHeight;
  }
}

Then I call that before drawing (could be in mouseMove for example). That way it only clears the canvas if the size changed.

You can also use canvas.getBoundingClientRect() and it is technically more correct but it also generates garbage which for my purposes is usually bad. For yours though I suspect it wouldn't matter.

Anytime you set/change the size of a canvas everything about it is reset and it is cleared. That means other settings like fillStyle, lineWidth etc are all reset to defaults when you change the size. If you want something to last past resizing you'll need to record it somehow. Ideas include keeping track of everything drawn so far and drawing it again after resizing. Another idea is to copy the current canvas to another offscreen canvas using something like

 // make a new canvas if we haven't already
 offscreenCanvas = offscreenCanvas || document.createElement("canvas");

 // make the offscreen canvas match the size of the onscreen canvas
 offscreenCanvas.width = onscreenCanvas.width;
 offscreenCanvas.height = onscreenCanvas.height;

 // get a context for a offscreen canvas
 offscreenCanvasContext = offscreenCanvas.getContext("2d");

 // clear it just in case it's old and the size didn't change.
 offscreenCanvasContext.clearRect(0, 0, offscreenCanvas.width, offscreenCanvas.height);

 // copy the onscreen canvas to the offscreen canvas
 offscreenCanvasContext.drawImage(onscreenCanvas, 0, 0);

 // resize the onscreen canvas
 reiszeCanvasToMatchDisplaySize(onscreenCanvas);

 // copy back the old content
 onscreenCanvasContext.drawImage(offscreenCanvas, 0, 0);

 // free the memory used by the offscreen canvas
 offscreenCanvas.width = 0;
 offscreenCanvas.height = 0;

Of course if the old canvas size was larger you're going to end up clipping content.

Also if your canvas's drawingBuffer size (the number of pixels) doesn't match the display size you can do the math to make the mouse position still match

Community
  • 1
  • 1
gman
  • 100,619
  • 31
  • 269
  • 393
  • Aaaah I see. I didn't understand the distinction between canvas.width and canvas.clientWidth. This code is very intuitive. Good job, thank you. –  Sep 12 '16 at 04:39