Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Canvas HTML5: Display rect coordinates on mousemove

I'm currently developing an image editor with HTML5 Canvas, and I'm having a problem detecting the image coordinates when mousing over the canvas.

I have replicated the problem in a code snipped with a rectangle:

Example

When:

  • The red part is the border of the canvas. The canvas size is 400x400 pixels.
  • The green part is a rectangle simulating the image. The rect size is 200x200 pixels.
  • On the mousemove event, is displaying at the bottom coordinates of the canvas.
  • The blue text, is what I want (right now the coordinates are not those, are of the canvas)

What I want and I don't know how to do it:

  • On mousemove, I want to display coordinates of the rect instead of the canvas coordinates. ie the (0, 0) of the canvas will become negative. And the displayed (0, 0) on the bottom should correspond with the blue text.
  • It should work also after changing the ZOOM property. The ZOOM in the example is 1, but after changing to 0.5 or 1.5 should work in the same way.

Code

Here below I share the code snipped to see if anyone can help me with that because I'm a little frustrated and I'm sure it's silly. Thanks a lot!

const RECT_SIZE = 200
const ZOOM = 1
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
const svgPoint = svg.createSVGPoint()
const xform = svg.createSVGMatrix()
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
const res = document.querySelector('.res')
const pt = transformedPoint(canvas.width / 2, canvas.height / 2)
const X = canvas.width / 2 - RECT_SIZE / 2
const Y = canvas.height / 2 - RECT_SIZE / 2

function transformedPoint(x, y) {
  svgPoint.x = x
  svgPoint.y = y
  return svgPoint.matrixTransform(xform.inverse())
}

function mousemove(e) {
  const { left, top } = canvas.getBoundingClientRect()
  res.textContent = `X: ${e.clientX - left} - Y: ${e.clientY - top}`
}

// SCALING CANVAS
ctx.translate(pt.x, pt.y)
ctx.scale(ZOOM, ZOOM)
ctx.translate(-pt.x, -pt.y)

// SETTING SOME DEFAULTS
ctx.lineWidth = 1
ctx.strokeStyle = 'green'
ctx.fillStyle = 'blue'

// DRAWING A REACTANGLE
ctx.beginPath()
ctx.strokeRect(X, Y, RECT_SIZE,  RECT_SIZE)

ctx.font = "12px Arial";
ctx.fillText("0,0", X - 5, Y-10);
ctx.fillText("200,200", X + RECT_SIZE - 15, Y + RECT_SIZE + 15);

ctx.closePath()
canvas {
  border: 1px solid red;
}
<canvas width="400" height="400" onmousemove="mousemove(event)"}></canvas>
<div class="res" />

Changing the line:

res.textContent = `X: ${e.clientX - left} - Y: ${e.clientY - top}`

to

res.textContent = `X: ${e.clientX - left - X} - Y: ${e.clientY - top - Y}`

Is displaying the correct coordinates when ZOOM = 1. However, is not displaying the correct coordinates after change the zoom.

like image 414
Aral Roca Avatar asked Sep 11 '20 11:09

Aral Roca


1 Answers

You have to add ZOOM value to the X and Y calculation like in the example below. I used Math.round method as the final X and Y value is a long fraction.

const RECT_SIZE = 200
const ZOOM = 1.4
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
const svgPoint = svg.createSVGPoint()
const xform = svg.createSVGMatrix()
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
const res = document.querySelector('.res')
const pt = transformedPoint(canvas.width / 2, canvas.height / 2)
const X = canvas.width / 2 - RECT_SIZE / 2;
const Y = canvas.height / 2 - RECT_SIZE / 2;

canvas.addEventListener('mousemove',(event)=>{
  const {top, left} = event.target.getBoundingClientRect();
  const zoomX = Math.round((event.clientX - left - (canvas.width / 2 - ((RECT_SIZE * ZOOM) / 2)))/ZOOM);
  const zoomY = Math.round((event.clientY - top - (canvas.height / 2 - ((RECT_SIZE * ZOOM) / 2)))/ZOOM);
  res.textContent = `X: ${zoomX} - Y: ${zoomY}`;

});

function transformedPoint(x, y) {
  svgPoint.x = x
  svgPoint.y = y
  return svgPoint.matrixTransform(xform.inverse())
}


// SCALING CANVAS
ctx.translate(pt.x, pt.y)
ctx.scale(ZOOM, ZOOM)
ctx.translate(-pt.x, -pt.y)

// SETTING SOME DEFAULTS
ctx.lineWidth = 1
ctx.strokeStyle = 'green'
ctx.fillStyle = 'blue'

// DRAWING A REACTANGLE
ctx.beginPath()
ctx.strokeRect(X, Y, RECT_SIZE,  RECT_SIZE)

ctx.font = "12px Arial";
ctx.fillText("0,0", X - 5, Y-10);
ctx.fillText("200,200", X + RECT_SIZE - 15, Y + RECT_SIZE + 15);

ctx.closePath()
canvas {
  border: 1px solid red;
}
<canvas width="400" height="400"}></canvas>
<div class="res" />
like image 81
Paweł Avatar answered Oct 19 '22 16:10

Paweł