Який найпростіший спосіб додати обробник подій клацання до елемента полотна, який поверне координати x та y клацання (відносно елемента полотна)?
Не потрібна сумісна версія браузера, Safari, Opera і Firefox.
Який найпростіший спосіб додати обробник подій клацання до елемента полотна, який поверне координати x та y клацання (відносно елемента полотна)?
Не потрібна сумісна версія браузера, Safari, Opera і Firefox.
Відповіді:
Редагувати 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, починаючи з самого елемента полотна. Цей код ідеально працює для мене, перевірений у 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;
Останній рядок полегшує отримання координат миші відносно елемента полотна. Все, що потрібно для отримання корисних координат, - це
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 (тестований w / 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 для абсолютного зміщення натискання сторінки, включаючи поле, накладку і т.д. раніше не існувало. Отже, для сумісності з трохи старшими браузерами ви можете використовувати:
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: стиль, що не застосовується, не буде повідомляти верхній лівий край своєї області вмісту як 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-coordinate/
<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
};
});
Це працює, оскільки зміщення полотна () та jqEvent.pageX / Y відносно документа незалежно від положення прокрутки.
Зауважте, що якщо ваше полотно масштабується, то ці координати не збігаються з логічними координатами полотна . Щоб отримати їх, ви також зробите:
var logicalCoords = {
x: coords.x * (canvas.width / $(canvas).width()),
y: coords.y * (canvas.height / $(canvas).height())
}
Тож це і проста, але трохи складніша тема, ніж здається.
По-перше, тут зазвичай виникають суперечливі питання
Як отримати відносні координатні елементи миші
Як отримати координати миші піксельного полотна для 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 вони не існують на події дотику , і тому не можуть бути використані з прошивкою 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/coordinate-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
що означає, що виділене число негайно
У прототипі використовуйте 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};
}
По-перше, як говорили інші, вам потрібна функція, щоб отримати положення елемента полотна . Ось метод, який трохи елегантніший, ніж деякі інші на цій сторінці (ІМХО). Ви можете передати йому будь-який елемент і отримати його положення в документі:
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);
Сподіваюся, що це допомагає.