Есть ли способ зарегистрировать клик по изображению, игнорируя прозрачные пиксели, в JavaScript? (например, если они нажали на круг или каплю) - PullRequest
0 голосов
/ 10 апреля 2020

Итак, я понимаю, как рассчитать, нажал ли пользователь внутри прямоугольника, но есть ли в любом случае зарегистрировать щелчок на холсте, который находится сверху BLOB-объекта, но не внутри прямоугольника, который определяет изображение BLOB-объекта? Как будто изображение - это капля с остальными прозрачными пикселями, но я не знаю, как (или если) JavaScript может распознать, щелкнули ли они внутри него.

1 Ответ

1 голос
/ 11 апреля 2020

Чтобы сделать это, вам нужно иметь возможность считывать пиксельные данные вашего изображения.

Элемент canvas и его 2D- и webgl-контексты являются единственными веб-API, способными сделать это, но чтобы это работало, вы должны обслуживать свое изображение в соответствии с тем же источником происхождения (то есть вы не можете сделать это с изображением, полученным с другого сервера).

Если Вы можете выполнить это требование, тогда это относительно просто:

  • нарисуйте свое изображение на холсте
  • захватите данные пикселей
  • в обработчике события мыши, преобразуйте координаты, поэтому они относятся к координатам изображения
  • проверить, является ли пиксель в координатах этого события прозрачным (0)

Вот простая демонстрация с использованием 2D-контекста и событие mousemove, вы также можете изменить его на щелчок:

onload = (evt) => { // wait for the resource to be fully loaded
  const img = document.getElementById('img');
  // we create a canvas element
  const canvas = document.createElement('canvas');
  // set the canvas the same size as the image
  const width = canvas.width = img.naturalWidth;
  const height = canvas.height = img.naturalHeight;
  // the 2D context
  const ctx = canvas.getContext('2d');
  // we draw the image on the canvas
  ctx.drawImage(img,0,0);
  // we get the ImageData out of it
  const img_data = ctx.getImageData( 0, 0, width, height );
  // the pixel data is an Uint8Array as [r,g,b,a,r,g,b,a...]
  // since we only want transparency, we can make an Uint32Array of it
  // this way one pixel takes one slot
  const pixels = new Uint32Array( img_data.data.buffer );
  
  // now we can start listening to mouse events
  img.onmousemove = (evt) => {
    // get the current BoundingBox of our element
    const bbox = img.getBoundingClientRect();
    // transform the event's coords
    const { x, y } = getRelativeCoords( evt, bbox, width, height );
    // the index in our linear array
    const px_index = (y * width) + x;
    // if this pixel is transparent, then it would be 0
    const over_color = pixels[ px_index ] !== 0;
    img.classList.toggle( 'over-color', over_color );
  };
};

function getRelativeCoords( mouseevt, bbox, original_width, original_height ) {
  // the position relative to the element
  const elem_x = mouseevt.clientX - bbox.left;
  const elem_y = mouseevt.clientY - bbox.top;
  // the ratio by which the image is shrinked/stretched
  const ratio_x = original_width / bbox.width;
  const ratio_y = original_height / bbox.height;
  // the real position in the the image's data
  const x = Math.round(elem_x * ratio_x);
  const y = Math.round(elem_y * ratio_y);

  return { x, y };
}
.over-color {
  cursor: pointer;
}
img { border: 1px solid; width: 300px }
<img id="img"
  src="https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png"
  crossorigin>
...