Так что это простая, но немного более сложная тема, чем кажется.
Во-первых, здесь обычно возникают спорные вопросы
Как получить элемент относительно координат мыши
Как получить координаты мыши в пиксельных холстах для 2D Canvas API или WebGL
итак, ответы
Как получить элемент относительно координат мыши
Является ли элемент холстом, получая элемент, относительные координаты мыши одинаковы для всех элементов.
На вопрос «Как получить относительные координаты мыши для холста» есть 2 простых ответа
Простой ответ № 1 используйте offsetX
и offsetY
canvas.addEventListner('mousemove', (e) => {
const x = e.offsetX;
const y = e.offsetY;
});
Этот ответ работает в Chrome, Firefox и Safari. В отличие от всех других значений событий offsetX
и offsetY
учитывают преобразования CSS.
Самая большая проблема с offsetX
и offsetY
заключается в том, что по состоянию на 2019/05 они не существуют на сенсорных событиях и поэтому не могут использоваться с iOS Safari. Они существуют в Pointer Events, которые существуют в Chrome и Firefox, но не в Safari, хотя , очевидно, Safari работает над этим .
Другая проблема - события должны быть на самом холсте. Если вы поместите их в какой-либо другой элемент или окно, вы не сможете позже выбрать холст, который будет вашим ориентиром.
Простой ответ №2 использовать clientX
, clientY
и canvas.getBoundingClientRect
Если вам не нужны преобразования CSS, следующий простейший ответ - позвонить canvas. getBoundingClientRect()
и вычесть левое из clientX
и top
из clientY
, как в
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
});
Это будет работать до тех пор, пока нет CSS-преобразований. Он также работает с сенсорными событиями и будет работать с Safari iOS
.
canvas.addEventListener('touchmove', (e) => {
const rect = canvas. getBoundingClientRect();
const x = e.touches[0].clientX - rect.left;
const y = e.touches[0].clientY - rect.top;
});
Как получить координаты мыши в пикселях Canvas для 2D Canvas API
Для этого нам нужно взять значения, которые мы получили выше, и преобразовать размер отображаемого холста в количество пикселей в самом холсте.
с canvas.getBoundingClientRect
и clientX
и clientY
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
const elementRelativeX = e.clientX - rect.left;
const elementRelativeY = e.clientY - rect.top;
const canvasRelativeX = elementRelativeX * canvas.width / rect.width;
const canvasRelativeY = elementRelativeY * canvas.height / rect.height;
});
или offsetX
и offsetY
canvas.addEventListener('mousemove', (e) => {
const elementRelativeX = e.offsetX;
const elementRelativeX = e.offsetY;
const canvasRelativeX = elementRelativeX * canvas.width / canvas.clientWidth;
const canvasRelativeY = elementRelativeX * canvas.height / canvas.clientHeight;
});
Примечание: Во всех случаях не добавляйте отступы или границы на холст. Это значительно усложнит код. Вместо того, чтобы использовать рамку или отступ, окружите холст каким-либо другим элементом и добавьте отступ и или границу к внешнему элементу.
Рабочий пример с использованием event.offsetX
, event.offsetY
[...document.querySelectorAll('canvas')].forEach((canvas) => {
const ctx = canvas.getContext('2d');
ctx.canvas.width = ctx.canvas.clientWidth;
ctx.canvas.height = ctx.canvas.clientHeight;
let count = 0;
function draw(e, radius = 1) {
const pos = {
x: e.offsetX * canvas.width / canvas.clientWidth,
y: e.offsetY * canvas.height / canvas.clientHeight,
};
document.querySelector('#debug').textContent = count;
ctx.beginPath();
ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2);
ctx.fillStyle = hsl((count++ % 100) / 100, 1, 0.5);
ctx.fill();
}
function preventDefault(e) {
e.preventDefault();
}
if (window.PointerEvent) {
canvas.addEventListener('pointermove', (e) => {
draw(e, Math.max(Math.max(e.width, e.height) / 2, 1));
});
canvas.addEventListener('touchstart', preventDefault, {passive: false});
canvas.addEventListener('touchmove', preventDefault, {passive: false});
} else {
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mousedown', preventDefault);
}
});
function hsl(h, s, l) {
return `hsl(${h * 360 | 0},${s * 100 | 0}%,${l * 100 | 0}%)`;
}
.scene {
width: 200px;
height: 200px;
perspective: 600px;
}
.cube {
width: 100%;
height: 100%;
position: relative;
transform-style: preserve-3d;
animation-duration: 16s;
animation-name: rotate;
animation-iteration-count: infinite;
animation-timing-function: linear;
}
@keyframes rotate {
from { transform: translateZ(-100px) rotateX( 0deg) rotateY( 0deg); }
to { transform: translateZ(-100px) rotateX(360deg) rotateY(720deg); }
}
.cube__face {
position: absolute;
width: 200px;
height: 200px;
display: block;
}
.cube__face--front { background: rgba(255, 0, 0, 0.2); transform: rotateY( 0deg) translateZ(100px); }
.cube__face--right { background: rgba(0, 255, 0, 0.2); transform: rotateY( 90deg) translateZ(100px); }
.cube__face--back { background: rgba(0, 0, 255, 0.2); transform: rotateY(180deg) translateZ(100px); }
.cube__face--left { background: rgba(255, 255, 0, 0.2); transform: rotateY(-90deg) translateZ(100px); }
.cube__face--top { background: rgba(0, 255, 255, 0.2); transform: rotateX( 90deg) translateZ(100px); }
.cube__face--bottom { background: rgba(255, 0, 255, 0.2); transform: rotateX(-90deg) translateZ(100px); }
Рабочий пример с использованием canvas.getBoundingClientRect
и event.clientX
и event.clientY
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
ctx.canvas.width = ctx.canvas.clientWidth;
ctx.canvas.height = ctx.canvas.clientHeight;
let count = 0;
function draw(e, radius = 1) {
const rect = canvas.getBoundingClientRect();
const pos = {
x: (e.clientX - rect.left) * canvas.width / canvas.clientWidth,
y: (e.clientY - rect.top) * canvas.height / canvas.clientHeight,
};
ctx.beginPath();
ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2);
ctx.fillStyle = hsl((count++ % 100) / 100, 1, 0.5);
ctx.fill();
}
function preventDefault(e) {
e.preventDefault();
}
if (window.PointerEvent) {
canvas.addEventListener('pointermove', (e) => {
draw(e, Math.max(Math.max(e.width, e.height) / 2, 1));
});
canvas.addEventListener('touchstart', preventDefault, {passive: false});
canvas.addEventListener('touchmove', preventDefault, {passive: false});
} else {
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mousedown', preventDefault);
}
function hsl(h, s, l) {
return `hsl(${h * 360 | 0},${s * 100 | 0}%,${l * 100 | 0}%)`;
}
canvas { background: #FED; }
<canvas width="400" height="100" style="width: 300px; height: 200px"></canvas>
<div>canvas deliberately has differnt CSS size vs drawingbuffer size</div>