Какой самый простой способ добавить обработчик события click к элементу canvas, который будет возвращать координаты x и y клика (относительно элемента canvas)?
Совместимость с устаревшими браузерами не требуется, Safari, Opera и Firefox подойдут.
Какой самый простой способ добавить обработчик события click к элементу canvas, который будет возвращать координаты x и y клика (относительно элемента canvas)?
Совместимость с устаревшими браузерами не требуется, Safari, Opera и Firefox подойдут.
Ответы:
Edit 2018: Ответ на этот вопрос довольно старый , и он использует для проверки старых браузеров, которые не нужно больше, как clientX
и clientY
свойства работают во всех современных браузерах. Возможно, вы захотите проверить ответ Patriques для более простого, более свежего решения.
Оригинальный ответ:
Как описано в статье, которую я нашел тогда, но больше не существует:
var x;
var y;
if (e.pageX || e.pageY) {
x = e.pageX;
y = e.pageY;
}
else {
x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}
x -= gCanvasElement.offsetLeft;
y -= gCanvasElement.offsetTop;
Работал отлично для меня.
Если вам нравится простота, но все же нужна кросс-браузерная функциональность, я нашел, что это решение подойдет мне лучше всего Это упрощение решения @ Aldekein, но без jQuery .
function getCursorPosition(canvas, event) {
const rect = canvas.getBoundingClientRect()
const x = event.clientX - rect.left
const y = event.clientY - rect.top
console.log("x: " + x + " y: " + y)
}
const canvas = document.querySelector('canvas')
canvas.addEventListener('mousedown', function(e) {
getCursorPosition(canvas, e)
})
getBoundingClientRect
возвращает позиции относительно порта просмотра? Тогда мое предположение было неверным. Я никогда не проверял это, так как это никогда не было проблемой для меня, и я любезно хотел бы предупредить других читателей о потенциальной проблеме, которую я видел, но спасибо за ваше разъяснение.
var canvas = document.getElementById('canvasID'); canvas.addEventListener("mousedown", function (e) { getCursorPosition(canvas, e);});
Обновление (5/5/16): вместо этого следует использовать ответ патриков , так как он проще и надежнее.
Поскольку холст не всегда стилизован относительно всей страницы, canvas.offsetLeft/Top
он не всегда возвращает то, что вам нужно. Он вернет количество пикселей, на которые он смещен, относительно его элемента offsetParent, который может быть чем-то вроде div
элемента, содержащего холст с примененным position: relative
стилем. Для этого вам нужно пройтись по цепочке offsetParent
s, начиная с самого элемента canvas. Этот код отлично работает для меня, протестирован в Firefox и Safari, но должен работать для всех.
function relMouseCoords(event){
var totalOffsetX = 0;
var totalOffsetY = 0;
var canvasX = 0;
var canvasY = 0;
var currentElement = this;
do{
totalOffsetX += currentElement.offsetLeft - currentElement.scrollLeft;
totalOffsetY += currentElement.offsetTop - currentElement.scrollTop;
}
while(currentElement = currentElement.offsetParent)
canvasX = event.pageX - totalOffsetX;
canvasY = event.pageY - totalOffsetY;
return {x:canvasX, y:canvasY}
}
HTMLCanvasElement.prototype.relMouseCoords = relMouseCoords;
Последняя строка упрощает получение координат мыши относительно элемента canvas. Все, что нужно для получения полезных координат, это
coords = canvas.relMouseCoords(event);
canvasX = coords.x;
canvasY = coords.y;
event.offsetX
и event.offsetY
атрибуты, поэтому я изменил свое решение, добавив if (event.offsetX !== undefined && event.offsetY !== undefined) { return {x:event.offsetX, y:event.offsetY}; }
. Похоже, это работает.
event.offsetX
и event.offsetY
также работает в IE9. Для Firefox (протестировано с v / v13) вы можете использовать event.layerX
и event.layerY
.
canvasX = event.pageX - totalOffsetX - document.body.scrollLeft; canvasY = event.pageY - totalOffsetY - document.body.scrollTop;
Современный браузер теперь справится с этим для вас. Chrome, IE9 и Firefox поддерживают смещение X / Y следующим образом, передавая событие из обработчика щелчка.
function getRelativeCoords(event) {
return { x: event.offsetX, y: event.offsetY };
}
Большинство современных браузеров также поддерживают layerX / Y, однако Chrome и IE используют layerX / Y для абсолютного смещения клика по странице, включая поля, отступы и т. Д. В Firefox layerX / Y и offsetX / Y эквивалентны, но смещение не было ранее не существовало. Итак, для совместимости с чуть более старыми браузерами вы можете использовать:
function getRelativeCoords(event) {
return { x: event.offsetX || event.layerX, y: event.offsetY || event.layerY };
}
Согласно свежему QuirksmodeclientX
и clientY
методы поддерживаются во всех основных браузерах. Итак, вот и все - хороший рабочий код, который работает в элементе прокрутки на странице с полосами прокрутки:
function getCursorPosition(canvas, event) {
var x, y;
canoffset = $(canvas).offset();
x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft - Math.floor(canoffset.left);
y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop - Math.floor(canoffset.top) + 1;
return [x,y];
}
Это также требует JQuery для $(canvas).offset()
.
Я сделал полную демонстрацию, которая работает в каждом браузере с полным исходным кодом решения этой проблемы: координаты щелчка мышью по Canvas в Javascript . Чтобы попробовать демо, скопируйте код и вставьте его в текстовый редактор. Затем сохраните его как example.html и, наконец, откройте файл в браузере.
Вот небольшая модификация ответа Райана Артеконы для холстов с переменной (%) шириной:
HTMLCanvasElement.prototype.relMouseCoords = function (event) {
var totalOffsetX = 0;
var totalOffsetY = 0;
var canvasX = 0;
var canvasY = 0;
var currentElement = this;
do {
totalOffsetX += currentElement.offsetLeft;
totalOffsetY += currentElement.offsetTop;
}
while (currentElement = currentElement.offsetParent)
canvasX = event.pageX - totalOffsetX;
canvasY = event.pageY - totalOffsetY;
// Fix for variable canvas width
canvasX = Math.round( canvasX * (this.width / this.offsetWidth) );
canvasY = Math.round( canvasY * (this.height / this.offsetHeight) );
return {x:canvasX, y:canvasY}
}
Будьте осторожны при выполнении преобразования координат; в событии щелчка возвращено несколько значений, не относящихся к браузеру. Использование clientX и clientY одного недостаточно, если прокручивается окно браузера (проверено в Firefox 3.5 и Chrome 3.0).
Этот причудливый режим статья о предоставляет более правильную функцию, которая может использовать pageX или pageY или комбинацию clientX с document.body.scrollLeft и clientY с document.body.scrollTop для вычисления координаты щелчка относительно источника документа.
ОБНОВЛЕНИЕ: Кроме того, offsetLeft и offsetTop относятся к дополнительному размеру элемента, а не к внутреннему размеру. Холст с примененным padding: style не будет отображать верхний левый угол своей области содержимого как offsetLeft. Существуют различные решения этой проблемы; самым простым может быть очистить все стили границ, отступов и т. д. на самом холсте и вместо этого применить их к блоку, содержащему холст.
Я не уверен, какой смысл всех этих ответов перебирают родительские элементы и делают всякие странные вещи .
HTMLElement.getBoundingClientRect
Метод предназначен для для обработки фактического положения экрана любого элемента. Это включает прокрутку, поэтому такие вещи scrollTop
не нужны:
(из MDN) Количество прокрутки, которая была сделана из области области просмотра (или любого другого прокручиваемого элемента ), учитывается при вычислении ограничивающего прямоугольника
Самый простой подход уже был опубликован здесь. Это правильно до тех пор, пока нет дикого CSS задействованы правила.
Если ширина пикселя изображения не совпадает с шириной CSS, вам нужно применить некоторое отношение к значениям пикселей:
/* Returns pixel coordinates according to the pixel that's under the mouse cursor**/
HTMLCanvasElement.prototype.relativeCoords = function(event) {
var x,y;
//This is the current screen rectangle of canvas
var rect = this.getBoundingClientRect();
var top = rect.top;
var bottom = rect.bottom;
var left = rect.left;
var right = rect.right;
//Recalculate mouse offsets to relative offsets
x = event.clientX - left;
y = event.clientY - top;
//Also recalculate offsets of canvas is stretched
var width = right - left;
//I use this to reduce number of calculations for images that have normal size
if(this.width!=width) {
var height = bottom - top;
//changes coordinates by ratio
x = x*(this.width/width);
y = y*(this.height/height);
}
//Return as an array
return [x,y];
}
Пока холст не имеет границы, он работает для растянутых изображений (jsFiddle) .
Если холст имеет толстую границу, все становится немного сложнее . Вам буквально нужно вычесть границу из ограничивающего прямоугольника. Это можно сделать с помощью .getComputedStyle . Этот ответ описывает процесс .
Затем функция немного растет:
/* Returns pixel coordinates according to the pixel that's under the mouse cursor**/
HTMLCanvasElement.prototype.relativeCoords = function(event) {
var x,y;
//This is the current screen rectangle of canvas
var rect = this.getBoundingClientRect();
var top = rect.top;
var bottom = rect.bottom;
var left = rect.left;
var right = rect.right;
//Subtract border size
// Get computed style
var styling=getComputedStyle(this,null);
// Turn the border widths in integers
var topBorder=parseInt(styling.getPropertyValue('border-top-width'),10);
var rightBorder=parseInt(styling.getPropertyValue('border-right-width'),10);
var bottomBorder=parseInt(styling.getPropertyValue('border-bottom-width'),10);
var leftBorder=parseInt(styling.getPropertyValue('border-left-width'),10);
//Subtract border from rectangle
left+=leftBorder;
right-=rightBorder;
top+=topBorder;
bottom-=bottomBorder;
//Proceed as usual
...
}
Я не могу думать ни о чем, что могло бы спутать эту последнюю функцию. Посмотрите на себя в JsFiddle .
Если вам не нравится изменять нативные prototype
s, просто измените функцию и вызовите ее (canvas, event)
(и замените любую this
на canvas
).
Вот очень хороший учебник
http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/
<canvas id="myCanvas" width="578" height="200"></canvas>
<script>
function writeMessage(canvas, message) {
var context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
context.font = '18pt Calibri';
context.fillStyle = 'black';
context.fillText(message, 10, 25);
}
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
canvas.addEventListener('mousemove', function(evt) {
var mousePos = getMousePos(canvas, evt);
var message = 'Mouse position: ' + mousePos.x + ',' + mousePos.y;
writeMessage(canvas, message);
}, false);
надеюсь это поможет!
width
/ height
переопределение, но все еще является одним из лучших решений.
Используя jQuery в 2016 году, чтобы получить координаты кликов относительно холста, я делаю:
$(canvas).click(function(jqEvent) {
var coords = {
x: jqEvent.pageX - $(canvas).offset().left,
y: jqEvent.pageY - $(canvas).offset().top
};
});
Это работает, так как и canvas offset (), и jqEvent.pageX / Y относятся к документу независимо от положения прокрутки.
Обратите внимание, что если ваш холст масштабируется, то эти координаты не совпадают с логическими координатами холста . Чтобы получить их, вы также должны сделать:
var logicalCoords = {
x: coords.x * (canvas.width / $(canvas).width()),
y: coords.y * (canvas.height / $(canvas).height())
}
Так что это простая, но немного более сложная тема, чем кажется.
Во-первых, здесь обычно возникают спорные вопросы.
Как получить элемент относительно координат мыши
Как получить координаты пиксельной мыши Canvas для 2D Canvas API или WebGL
итак, ответы
Является ли элемент холстом, получая элемент относительно координат мыши, одинаково для всех элементов.
На вопрос «Как получить относительные координаты мыши для холста» есть 2 простых ответа.
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 работает над этим .
Еще одна проблема - события должны быть на самом холсте. Если вы поместите их в какой-то другой элемент или окно, вы не сможете позже выбрать холст, который будет вашим ориентиром.
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.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
Рабочий пример с использованием canvas.getBoundingClientRect
и event.clientX
иevent.clientY
Я рекомендую эту ссылку - http://miloq.blogspot.in/2011/05/coordinates-mouse-click-canvas.html.
<style type="text/css">
#canvas{background-color: #000;}
</style>
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", init, false);
function init()
{
var canvas = document.getElementById("canvas");
canvas.addEventListener("mousedown", getPosition, false);
}
function getPosition(event)
{
var x = new Number();
var y = new Number();
var canvas = document.getElementById("canvas");
if (event.x != undefined && event.y != undefined)
{
x = event.x;
y = event.y;
}
else // Firefox method to get the position
{
x = event.clientX + document.body.scrollLeft +
document.documentElement.scrollLeft;
y = event.clientY + document.body.scrollTop +
document.documentElement.scrollTop;
}
x -= canvas.offsetLeft;
y -= canvas.offsetTop;
alert("x: " + x + " y: " + y);
}
</script>
x = new Number()
? Приведенный ниже код переназначает, x
что означает, что выделенное число сразу же отбрасывается
В Prototype используйте cumulativeOffset (), чтобы выполнить рекурсивное суммирование, как упомянуто Райаном Артеконой выше.
Вы могли бы просто сделать:
var canvas = yourCanvasElement;
var mouseX = (event.clientX - (canvas.offsetLeft - canvas.scrollLeft)) - 2;
var mouseY = (event.clientY - (canvas.offsetTop - canvas.scrollTop)) - 2;
Это даст вам точное положение указателя мыши.
Смотрите демонстрацию на http://jsbin.com/ApuJOSA/1/edit?html,output .
function mousePositionOnCanvas(e) {
var el=e.target, c=el;
var scaleX = c.width/c.offsetWidth || 1;
var scaleY = c.height/c.offsetHeight || 1;
if (!isNaN(e.offsetX))
return { x:e.offsetX*scaleX, y:e.offsetY*scaleY };
var x=e.pageX, y=e.pageY;
do {
x -= el.offsetLeft;
y -= el.offsetTop;
el = el.offsetParent;
} while (el);
return { x: x*scaleX, y: y*scaleY };
}
Вот некоторые модификации вышеупомянутого решения Райана Артеконы.
function myGetPxStyle(e,p)
{
var r=window.getComputedStyle?window.getComputedStyle(e,null)[p]:"";
return parseFloat(r);
}
function myGetClick=function(ev)
{
// {x:ev.layerX,y:ev.layerY} doesn't work when zooming with mac chrome 27
// {x:ev.clientX,y:ev.clientY} not supported by mac firefox 21
// document.body.scrollLeft and document.body.scrollTop seem required when scrolling on iPad
// html is not an offsetParent of body but can have non null offsetX or offsetY (case of wordpress 3.5.1 admin pages for instance)
// html.offsetX and html.offsetY don't work with mac firefox 21
var offsetX=0,offsetY=0,e=this,x,y;
var htmls=document.getElementsByTagName("html"),html=(htmls?htmls[0]:0);
do
{
offsetX+=e.offsetLeft-e.scrollLeft;
offsetY+=e.offsetTop-e.scrollTop;
} while (e=e.offsetParent);
if (html)
{
offsetX+=myGetPxStyle(html,"marginLeft");
offsetY+=myGetPxStyle(html,"marginTop");
}
x=ev.pageX-offsetX-document.body.scrollLeft;
y=ev.pageY-offsetY-document.body.scrollTop;
return {x:x,y:y};
}
Во-первых, как уже говорили другие, вам нужна функция, чтобы получить положение элемента canvas . Вот метод, который немного более элегантен, чем некоторые другие на этой странице (IMHO). Вы можете передать ему любой элемент и получить его положение в документе:
function findPos(obj) {
var curleft = 0, curtop = 0;
if (obj.offsetParent) {
do {
curleft += obj.offsetLeft;
curtop += obj.offsetTop;
} while (obj = obj.offsetParent);
return { x: curleft, y: curtop };
}
return undefined;
}
Теперь вычислите текущую позицию курсора относительно этого:
$('#canvas').mousemove(function(e) {
var pos = findPos(this);
var x = e.pageX - pos.x;
var y = e.pageY - pos.y;
var coordinateDisplay = "x=" + x + ", y=" + y;
writeCoordinateDisplay(coordinateDisplay);
});
Обратите внимание, что я отделил универсальную findPos
функцию от кода обработки событий. ( Так и должно быть . Мы должны стараться, чтобы наши функции выполнялись по одной задаче.)
Значения offsetLeft
и offsetTop
относительно offsetParent
, которые могут быть каким-то div
узлом- оберткой (или что-то еще, в этом отношении). Когда нет элементов, обертывающих canvas
их, они относительно body
, поэтому нет смещения для вычитания. Вот почему нам нужно определить положение холста, прежде чем мы сможем сделать что-то еще.
Похоже, e.pageX
и e.pageY
задайте положение курсора относительно документа. Вот почему мы вычитаем смещение холста из этих значений, чтобы получить истинную позицию.
Альтернативой для позиционируемых элементов является непосредственное использование значений e.layerX
и e.layerY
. Это менее надежно, чем описанный выше метод, по двум причинам:
ThreeJS r77
var x = event.offsetX == undefined ? event.layerX : event.offsetX;
var y = event.offsetY == undefined ? event.layerY : event.offsetY;
mouse2D.x = ( x / renderer.domElement.width ) * 2 - 1;
mouse2D.y = - ( y / renderer.domElement.height ) * 2 + 1;
Попробовав много решений. Это сработало для меня. Может помочь кому-то еще, следовательно, размещения. Получил это отсюда
Вот упрощенное решение (это не работает с границами / прокруткой):
function click(event) {
const bound = event.target.getBoundingClientRect();
const xMult = bound.width / can.width;
const yMult = bound.height / can.height;
return {
x: Math.floor(event.offsetX / xMult),
y: Math.floor(event.offsetY / yMult),
};
}
Я создавал приложение, имеющее холст поверх PDF, которое включало в себя множество изменений размера холста, таких как увеличение и уменьшение размера PDF, и, в свою очередь, при каждом увеличении / уменьшении PDF мне приходилось изменять размер холста, чтобы адаптировать его. размер pdf, я просмотрел множество ответов в stackOverflow и не нашел идеального решения, которое в конечном итоге решит проблему.
Я использовал rxjs и angular 6 и не нашел ответа, относящегося к самой новой версии.
Вот весь фрагмент кода, который был бы полезен для тех, кто использует rxjs для рисования поверх холста.
private captureEvents(canvasEl: HTMLCanvasElement) {
this.drawingSubscription = fromEvent(canvasEl, 'mousedown')
.pipe(
switchMap((e: any) => {
return fromEvent(canvasEl, 'mousemove')
.pipe(
takeUntil(fromEvent(canvasEl, 'mouseup').do((event: WheelEvent) => {
const prevPos = {
x: null,
y: null
};
})),
takeUntil(fromEvent(canvasEl, 'mouseleave')),
pairwise()
)
})
)
.subscribe((res: [MouseEvent, MouseEvent]) => {
const rect = this.cx.canvas.getBoundingClientRect();
const prevPos = {
x: Math.floor( ( res[0].clientX - rect.left ) / ( rect.right - rect.left ) * this.cx.canvas.width ),
y: Math.floor( ( res[0].clientY - rect.top ) / ( rect.bottom - rect.top ) * this.cx.canvas.height )
};
const currentPos = {
x: Math.floor( ( res[1].clientX - rect.left ) / ( rect.right - rect.left ) * this.cx.canvas.width ),
y: Math.floor( ( res[1].clientY - rect.top ) / ( rect.bottom - rect.top ) * this.cx.canvas.height )
};
this.coordinatesArray[this.file.current_slide - 1].push(prevPos);
this.drawOnCanvas(prevPos, currentPos);
});
}
А вот фрагмент, который фиксирует координаты мыши относительно размера холста, независимо от того, как вы увеличиваете / уменьшаете холст.
const prevPos = {
x: Math.floor( ( res[0].clientX - rect.left ) / ( rect.right - rect.left ) * this.cx.canvas.width ),
y: Math.floor( ( res[0].clientY - rect.top ) / ( rect.bottom - rect.top ) * this.cx.canvas.height )
};
const currentPos = {
x: Math.floor( ( res[1].clientX - rect.left ) / ( rect.right - rect.left ) * this.cx.canvas.width ),
y: Math.floor( ( res[1].clientY - rect.top ) / ( rect.bottom - rect.top ) * this.cx.canvas.height )
};
Привет, это в додзё, просто потому, что у меня уже есть код для проекта.
Должно быть достаточно очевидно, как преобразовать его обратно в не-додзё ванильный JavaScript.
function onMouseClick(e) {
var x = e.clientX;
var y = e.clientY;
}
var canvas = dojo.byId(canvasId);
dojo.connect(canvas,"click",onMouseClick);
Надеюсь, это поможет.