Получить номер пикселя при наведении мышкой

Всем привет! У меня есть канвас размером 500 на 500 пикселей. С помощью мышки его можно масштабировать и перемещаться по нему. Никак не могу придумать как получить номер пикселя (номер состоит из двух цифр x и y) на который сейчас смотрит мышка.

В будущем хотел бы при наведении мышки на пиксель рисовать вокруг него небольшую границу,

Вот пример в виде картинки (мы типо смотрим на правый низ канваса) : пример

Вот собственно код (сам канвас, зум и перемещение):

const ctx = canvas.getContext("2d");
canvas.width = 500;
canvas.height = 500;
const rand = (m = 255, M = m + (m = 0)) => (Math.random() * (M - m) + m) | 0;


const objects = [];
for (let i = 0; i < 100; i++) {
  objects.push({x: rand(canvas.width), y: rand(canvas.height),w: rand(40),h: rand(40), col: `rgb(${rand()},${rand()},${rand()})`});
}

requestAnimationFrame(drawCanvas); 

const view = (() => {
  const matrix = [1, 0, 0, 1, 0, 0]; // current view transform
  var m = matrix;             // alias 
  var scale = 1;              // current scale
  var ctx;                    // reference to the 2D context
  const pos = { x: 0, y: 0 }; // current position of origin
  var dirty = true;
  const API = {
    set context(_ctx) { ctx = _ctx; dirty = true },
    apply() {
      if (dirty) { this.update() }
      ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5])
    },
    get scale() { return scale },
    get position() { return pos },
    isDirty() { return dirty },
    update() {
      dirty = false;
      m[3] = m[0] = scale;
      m[2] = m[1] = 0;
      m[4] = pos.x;
      m[5] = pos.y;
    },
    pan(amount) {
      if (dirty) { this.update() }
       pos.x += amount.x;
       pos.y += amount.y;
       dirty = true;
    },
    scaleAt(at, amount) { // at in screen coords
      if (dirty) { this.update() }
      scale *= amount;
      pos.x = at.x - (at.x - pos.x) * amount;
      pos.y = at.y - (at.y - pos.y) * amount;
      dirty = true;
    },
  };
  return API;
})();
view.context = ctx;
function drawCanvas() {
    if (view.isDirty()) { 
        ctx.setTransform(1, 0, 0, 1, 0, 0); 
        ctx.clearRect(0, 0, canvas.width, canvas.height);

        view.apply(); // set the 2D context transform to the view
        for (i = 0; i < objects.length; i++) {
            var obj = objects[i];
            ctx.fillStyle = obj.col;
            ctx.fillRect(obj.x, obj.y, obj.h, obj.h);
        }
    }
    requestAnimationFrame(drawCanvas);
}


canvas.addEventListener("mousemove", mouseEvent, {passive: true});
canvas.addEventListener("mousedown", mouseEvent, {passive: true});
canvas.addEventListener("mouseup", mouseEvent, {passive: true});
canvas.addEventListener("mouseout", mouseEvent, {passive: true});
canvas.addEventListener("wheel", mouseWheelEvent, {passive: false});
const mouse = {x: 0, y: 0, oldX: 0, oldY: 0, button: false};
function mouseEvent(event) {
    if (event.type === "mousedown") { mouse.button = true }
    if (event.type === "mouseup" || event.type === "mouseout") { mouse.button = false }
    mouse.oldX = mouse.x;
    mouse.oldY = mouse.y;
    mouse.x = event.offsetX;
    mouse.y = event.offsetY    
    if(mouse.button) { // pan
        view.pan({x: mouse.x - mouse.oldX, y: mouse.y - mouse.oldY});
    }
}
function mouseWheelEvent(event) {
    var x = event.offsetX;
    var y = event.offsetY;
    if (event.deltaY < 0) { view.scaleAt({x, y}, 1.1) }
    else { view.scaleAt({x, y}, 1 / 1.1) }
    event.preventDefault();
}
body {
  background: gainsboro;
  margin: 0;
}
canvas {
  background: white;
  box-shadow: 1px 1px 1px rgba(0, 0, 0, .2);
}
<canvas id="canvas"></canvas>

Как рисовать границу при наведении на пиксель понятно, нужно взять offsetX и разделить его на ширину пикселя, мы получим номер пикселя, на который смотрим, далее рисуем квадратную границу вокруг него. Не очень понятно откуда эту самую ширину пикселя взять (при каждом изменении зума она будет меняться)

Из кода видно, что часть пикселей как бы остаются за экраном и их нужно как то учесть при получении номера


Ответы (1 шт):

Автор решения: 2ip

Если я вас правильно понял, то получается такое решение:

    const ctx = canvas.getContext("2d");
canvas.width = 500;
canvas.height = 500;
const rand = (m = 255, M = m + (m = 0)) => (Math.random() * (M - m) + m) | 0;
//Рисуем кусочек сетки
let div = document.createElement('div');
div.className = "none";
div.innerHTML = '<canvas id="gridform" width="'+20+'"height="'+20+'"></canvas>';
document.body.append(div);
var gridCanvas = document.getElementById("gridform");
const gctx = gridCanvas.getContext("2d");
gctx.strokeRect(0, 0, 20, 20);
gridCanvas.remove();
//
const objects = [];
for (let i = 0; i < 100; i++) {
  objects.push({x: rand(canvas.width), y: rand(canvas.height),w: rand(40),h: rand(40), col: `rgb(${rand()},${rand()},${rand()})`,type: 1});//для облегчения добавления сетки (выделения) я добавил тип обьекта, 1 - обычный случайный квадрат, 2 - сетка
}
objects.push({x: 0, y: 0, w: 0, h:0,col: 'rgb(0,0,0)',type: 2}) //добавим начальное положение выделения (сетки)
requestAnimationFrame(drawCanvas); 

const view = (() => {
  const matrix = [1, 0, 0, 1, 0, 0]; // current view transform
  var m = matrix;             // alias 
  var scale = 1;              // current scale
  var ctx;                    // reference to the 2D context
  const pos = { x: 0, y: 0 }; // current position of origin
  var dirty = true;
  const API = {
set context(_ctx) { ctx = _ctx; dirty = true },
apply() {
  if (dirty) { this.update() }
  ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5])
},
get scale() { return scale },
get position() { return pos },
isDirty() { return dirty },
update() {
  dirty = false;
  m[3] = m[0] = scale;
  m[2] = m[1] = 0;
  m[4] = pos.x;
  m[5] = pos.y;
},
pan(amount) {
  if (dirty) { this.update() }
   pos.x += amount.x;
   pos.y += amount.y;
   dirty = true;
},
scaleAt(at, amount) { // at in screen coords
  if (dirty) { this.update() }
  scale *= amount;
  pos.x = at.x - (at.x - pos.x) * amount;
  pos.y = at.y - (at.y - pos.y) * amount;
  dirty = true;
},
  };
  return API;
})();
view.context = ctx;
function drawCanvas() {
   
  

if (view.isDirty()) { 
  
    ctx.setTransform(1, 0, 0, 1, 0, 0); 
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    view.apply(); // set the 2D context transform to the view
    for (i = 0; i < objects.length; i++) {
        var obj = objects[i];
        ctx.fillStyle = obj.col;
        if(obj.type == 1){ //если обьект случайный квадрат
        ctx.fillRect(obj.x, obj.y, obj.h, obj.h);
        }else{// если обьект кусочек сетки
           
            ctx.drawImage(gridCanvas, obj.x, obj.y, obj.h, obj.h);
            
        }
    }
    
}
/////

requestAnimationFrame(drawCanvas);
}


canvas.addEventListener("mousemove", mouseEvent, {passive: true});
canvas.addEventListener("mousedown", mouseEvent, {passive: true});
canvas.addEventListener("mouseup", mouseEvent, {passive: true});
canvas.addEventListener("mouseout", mouseEvent, {passive: true});
canvas.addEventListener("wheel", mouseWheelEvent, {passive: false});
const mouse = {x: 0, y: 0, oldX: 0, oldY: 0, button: false};
function mouseEvent(event) {
//Отображаем сетку при перемещении курсора
objects.pop(); //удаляем тело старой сетки
objects.push({x: Math.trunc(((view.position.x-mouse.x)*-1)/view.scale), y:  Math.trunc(((view.position.y-mouse.y)*-1)/view.scale),w: 1,h: 1, col: `rgb(0,0,0)`, type:2}); //добавляем новую сетку
view.pan({x:0, y: 0}); //перемещаем поле на 0 пикселей, это заставит программу перерисовать поле и добавить нашу сетку 
//
if (event.type === "mousedown") { mouse.button = true }
if (event.type === "mouseup" || event.type === "mouseout") { mouse.button = false }
mouse.oldX = mouse.x;
mouse.oldY = mouse.y;
mouse.x = event.offsetX;
mouse.y = event.offsetY    
if(mouse.button) { // pan
    view.pan({x: mouse.x - mouse.oldX, y: mouse.y - mouse.oldY});

    //Тут мы определяем нужный нам пиксель
    document.getElementById("0").innerHTML = "по X: " + Math.trunc(((view.position.x-mouse.x)*-1)/view.scale)
    document.getElementById("1").innerHTML = "по Y: " + Math.trunc(((view.position.y-mouse.y)*-1)/view.scale)
    //
}

}
function mouseWheelEvent(event) {
var x = event.offsetX;
var y = event.offsetY;
if (event.deltaY < 0) { view.scaleAt({x, y}, 1.1) }
else { view.scaleAt({x, y}, 1 / 1.1) }
event.preventDefault();
}
    body {
  background: gainsboro;
  margin: 0;
}
canvas {
  background: white;
  box-shadow: 1px 1px 1px rgba(0, 0, 0, .2);
}
    <canvas id="canvas"></canvas>
<h4>Координаты пикселя:</h4>
<p id='0'> по X:</p>
<p id='1'> по Y:</p>

→ Ссылка