React Canvas,在鼠标点击位置检测图像中的区域
React Canvas, detect region in image at mouse click position
我有一张 canvas 这张图片
我想要当我点击进入时 canvas 我会在我点击的位置
例如,当我点击红点时,我将得到区域 1-2-3-4-5-6
// input: mousePosition: {x, y}
// output: listPoint: [
// {x1, y1},
// {x2, y2},
// ...
// {x6, y6}
// ]
有没有人有任何想法或任何工具可以帮助我。谢谢
这可以通过 HTML map areas 实现。我冒昧地根据你的图像在 500px x 450px 区域绘制坐标。
创建不同的区域后,您可以使每个区域包含一个 href
属性,该属性可以转到该区域的页面或使用 URL 执行 JavaScript 代码格式:javascript:insert_your_code_here
.
如果你想得到哪个区域在坐标 x, y
上,你可以在所述坐标上模拟点击事件。
结合所有这些,这是我想出的代码:
document.querySelector("#submitCoords").addEventListener("click", () => {
const x = document.querySelector("#xInput").value;
const y = document.querySelector("#yInput").value;
document.elementFromPoint(x, y).click();
});
const clicked = (area) => {
document.querySelector(
"#clickResults"
).innerText = `Clicked ${area} area.`;
};
<img src="https://i.imgur.com/myv6wss.jpeg" id="image" alt="Image" usemap="#workmap" width="500" height="450" />
<map name="workmap" id="workmap">
<area
shape="poly"
coords="46,46,223,53,213,183,101,203,46,199"
alt="A area"
href="javascript:clicked('A')"
/>
<area
shape="poly"
coords="409,57,446,57,446,348,390,262"
alt="B area"
href="javascript:clicked('B')"
/>
<area
shape="poly"
coords="46,199,101,203,134,298,148,340,46,340"
alt="C area"
href="javascript:clicked('C')"
/>
<area
shape="poly"
coords="148,340,134,298,390,262,446,348"
alt="D area"
href="javascript:clicked('D')"
/>
<area
shape="poly"
coords="223,53,409,57,390,262,134,298,101,203,213,183"
alt="Center area"
href="javascript:clicked('Center')"
/>
</map>
<br />
<input type="number" placeholder="Input x coordinate here" id="xInput" />
<input type="number" placeholder="Input y coordinate here" id="yInput" />
<br />
<br />
<button id="submitCoords">Submit coordinates</button>
<p id="clickResults"></p>
如果您有点和形状的列表,您可以使用 click
事件找出您所在的位置。
'Alternative approach' 来自 this 在 math.stackexchange.com
上的回答
为了确定我们是否在一个形状中,我们从鼠标点击的地方到我们 canvas 的外部取一条线段。然后我们计算它与我们形状的边缘相交的次数。如果交叉点的数量是奇数,那么我们就是这个形状。然后我们对每个形状都这样做,直到找到鼠标所在的位置。
要计算两条线是否相交,我们可以使用 this GeeksforGeeks 文章中的数学。它通过比较线中点的方向来工作。
我在下面有一个例子。
let globalPoints = [
{"x":155,"y":395,"n":"A"}/*0*/,
{"x":235,"y":630,"n":"B"}/*1*/,
{"x":0,"y":630,"n":"C"}/*2*/,
{"x":0,"y":380,"n":"D"}/*3*/,
{"x":0,"y":80,"n":"E"}/*4*/,
{"x":405,"y":80,"n":"F"}/*5*/,
{"x":385,"y":355,"n":"G"}/*6*/,
{"x":224,"y":598,"n":"H"}/*7*/,
{"x":755,"y":520,"n":"I"}/*8*/,
{"x":795,"y":80,"n":"J"}/*9*/,
{"x":890,"y":85,"n":"K"}/*10*/,
{"x":890,"y":630,"n":"L"}/*11*/,
{"x":855,"y":630,"n":"M"}/*12*/,
{"x":0,"y":0,"n":"N"}/*13*/,
{"x":890,"y":0,"n":"O"}/*14*/
];
let shapePoints = [
[0,1,2,3],
[3,4,5,6,0],
[0,7,8,9,5,6],
[9,10,11,12,8],
[9,10,11,12,8],
[7,1,12,8],
[4,13,14,10,9,5]
];
let lastClick = null;
class Shape {
constructor(points)
{
this.points = points;
this.filledIn = false;
}
draw(ctx) {
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.lineWidth = 3;
ctx.fillStyle = 'red';
ctx.moveTo(globalPoints[this.points[0]].x,globalPoints[this.points[0]].y);
for (let i =1;i < this.points.length;i++) {
ctx.lineTo(globalPoints[this.points[i]].x,globalPoints[this.points[i]].y);
}
ctx.closePath();
if (this.filledIn) {
ctx.fill();
}
ctx.stroke();
}
toString()
{
let name = "";
for (let i=0;i<this.points.length;i++) {
name += globalPoints[this.points[i]].n;
}
return name;
}
//use the 'alternate method' from this answer
//https://math.stackexchange.com/questions/926559/when-is-a-point-in-the-plane-inside-a-simple-closed-path
calculateMouseClick(mouseX,mouseY)
{
//generate a ray that extends to outside of the shape,
//we know -1,-1 will be outside as all points are positive
let ray = {"p1":{"x":mouseX,"y":mouseY},"p2":{"x":-1,"y":-1}};
let numIntersections = 0;
//calculate all but last lines
for (let i=0;i<this.points.length-1;i++) {
let shapeEdge = {
"p1":{
"x":globalPoints[this.points[i]].x,
"y":globalPoints[this.points[i]].y
},
"p2":{
"x":globalPoints[this.points[i+1]].x,
"y":globalPoints[this.points[i+1]].y
}
};
if (doIntersect(ray.p1,ray.p2,shapeEdge.p1,shapeEdge.p2)) {
numIntersections++;
}
}
//last line wraps around to start of array
let shapeEdge = {
"p1":{
"x":globalPoints[this.points[0]].x,
"y":globalPoints[this.points[0]].y
},
"p2":{
"x":globalPoints[this.points[this.points.length-1]].x,
"y":globalPoints[this.points[this.points.length-1]].y
}
};
if (doIntersect(ray.p1,ray.p2,shapeEdge.p1,shapeEdge.p2)) {
numIntersections++;
}
if (numIntersections == 0 || numIntersections%2 == 0) {
return false;
}
return true;
}
}
let shapes = [];
function init()
{
"use strict";
for (let i = 0;i< globalPoints.length;i++) {
//add 50 to x and y so we don't draw at the very edge of the canvas
globalPoints[i].x += 50;
globalPoints[i].y += 80;
}
for (let i = 0;i < shapePoints.length;i++) {
shapes.push(new Shape(shapePoints[i]));
}
draw();
let canvas = document.getElementById("drawingArea");
canvas.addEventListener("click",canvasClicked);
}
function draw()
{
let canvas = document.getElementById("drawingArea");
let ctx = canvas.getContext('2d');
ctx.clearRect(0,0,canvas.width,canvas.height);
drawShapes(ctx);
drawPoints(ctx);
drawClick(ctx);
}
function drawShapes(ctx)
{
"use strict";
for (let i = 0;i<shapes.length;i++) {
shapes[i].draw(ctx);
}
}
function drawPoints(ctx)
{
"use strict";
ctx.font = "48px sans";
for (let i = 0;i < globalPoints.length;i++) {
ctx.fillStyle = "black";
ctx.beginPath();
ctx.arc(globalPoints[i].x,globalPoints[i].y,5,0,2*Math.PI);
ctx.fill();
ctx.fillStyle = "#444444";
ctx.fillText(globalPoints[i].n,globalPoints[i].x-40,globalPoints[i].y-25);
}
}
function drawClick(ctx)
{
"use strict";
if (lastClick == null) {
return;
}
ctx.strokeStyle = "#33dd33";
ctx.arc(lastClick.x,lastClick.y,10,0,2*Math.PI);
ctx.fill();
}
function canvasClicked(event)
{
"use strict";
let mouseX = event.offsetX;
let mouseY = event.offsetY;
lastClick = {"x":mouseX,"y":mouseY};
//clear out previous click
for (let i=0;i<shapes.length;i++) {
shapes[i].filledIn = false;
}
let foundShape = false;
let shapeNameElem = document.getElementById("shapeName");
for (let i=0;i<shapes.length;i++) {
let isIn = shapes[i].calculateMouseClick(mouseX,mouseY);
if (isIn) {
shapes[i].filledIn = true;
foundShape = true;
shapeNameElem.innerHTML = "Clicked shape: "+shapes[i].toString();
break;
}
}
if (!foundShape) {
shapeNameElem.innerHTML="";
}
draw();
}
/********************************
onSegment(), orientation(), doIntersect()
https://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/
license CC BY-SA
https://www.geeksforgeeks.org/copyright-information/
********************************/
// Given three colinear points p, q, r, the function checks if
// point q lies on line segment 'pr'
function onSegment(p, q, r)
{
if (q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) &&
q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y))
return true;
return false;
}
// To find orientation of ordered triplet (p, q, r).
// The function returns following values
// 0 --> p, q and r are colinear
// 1 --> Clockwise
// 2 --> Counterclockwise
function orientation(p, q, r)
{
// See https://www.geeksforgeeks.org/orientation-3-ordered-points/
// for details of below formula.
let val = (q.y - p.y) * (r.x - q.x) -
(q.x - p.x) * (r.y - q.y);
if (val == 0) return 0; // colinear
return (val > 0)? 1: 2; // clock or counterclock wise
}
// The main function that returns true if line segment 'p1q1'
// and 'p2q2' intersect.
function doIntersect(p1, q1, p2, q2)
{
// Find the four orientations needed for general and
// special cases
let o1 = orientation(p1, q1, p2);
let o2 = orientation(p1, q1, q2);
let o3 = orientation(p2, q2, p1);
let o4 = orientation(p2, q2, q1);
// General case
if (o1 != o2 && o3 != o4)
return true;
// Special Cases
// p1, q1 and p2 are colinear and p2 lies on segment p1q1
if (o1 == 0 && onSegment(p1, p2, q1)) return true;
// p1, q1 and q2 are colinear and q2 lies on segment p1q1
if (o2 == 0 && onSegment(p1, q2, q1)) return true;
// p2, q2 and p1 are colinear and p1 lies on segment p2q2
if (o3 == 0 && onSegment(p2, p1, q2)) return true;
// p2, q2 and q1 are colinear and q1 lies on segment p2q2
if (o4 == 0 && onSegment(p2, q1, q2)) return true;
return false; // Doesn't fall in any of the above cases
}
window.addEventListener("load",init);
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<script src="test.js"></script>
<link rel="stylesheet" href="test.css"/>
</head>
<body>
<p id="shapeName"></p>
<canvas id="drawingArea" width="1000" height="800"></canvas>
</body>
</html>
没有图像中的点列表似乎是不可能的,我找到了另一个解决方案:(
我有一张 canvas 这张图片
我想要当我点击进入时 canvas 我会在我点击的位置
例如,当我点击红点时,我将得到区域 1-2-3-4-5-6
// input: mousePosition: {x, y}
// output: listPoint: [
// {x1, y1},
// {x2, y2},
// ...
// {x6, y6}
// ]
有没有人有任何想法或任何工具可以帮助我。谢谢
这可以通过 HTML map areas 实现。我冒昧地根据你的图像在 500px x 450px 区域绘制坐标。
创建不同的区域后,您可以使每个区域包含一个 href
属性,该属性可以转到该区域的页面或使用 URL 执行 JavaScript 代码格式:javascript:insert_your_code_here
.
如果你想得到哪个区域在坐标 x, y
上,你可以在所述坐标上模拟点击事件。
结合所有这些,这是我想出的代码:
document.querySelector("#submitCoords").addEventListener("click", () => {
const x = document.querySelector("#xInput").value;
const y = document.querySelector("#yInput").value;
document.elementFromPoint(x, y).click();
});
const clicked = (area) => {
document.querySelector(
"#clickResults"
).innerText = `Clicked ${area} area.`;
};
<img src="https://i.imgur.com/myv6wss.jpeg" id="image" alt="Image" usemap="#workmap" width="500" height="450" />
<map name="workmap" id="workmap">
<area
shape="poly"
coords="46,46,223,53,213,183,101,203,46,199"
alt="A area"
href="javascript:clicked('A')"
/>
<area
shape="poly"
coords="409,57,446,57,446,348,390,262"
alt="B area"
href="javascript:clicked('B')"
/>
<area
shape="poly"
coords="46,199,101,203,134,298,148,340,46,340"
alt="C area"
href="javascript:clicked('C')"
/>
<area
shape="poly"
coords="148,340,134,298,390,262,446,348"
alt="D area"
href="javascript:clicked('D')"
/>
<area
shape="poly"
coords="223,53,409,57,390,262,134,298,101,203,213,183"
alt="Center area"
href="javascript:clicked('Center')"
/>
</map>
<br />
<input type="number" placeholder="Input x coordinate here" id="xInput" />
<input type="number" placeholder="Input y coordinate here" id="yInput" />
<br />
<br />
<button id="submitCoords">Submit coordinates</button>
<p id="clickResults"></p>
如果您有点和形状的列表,您可以使用 click
事件找出您所在的位置。
'Alternative approach' 来自 this 在 math.stackexchange.com
上的回答为了确定我们是否在一个形状中,我们从鼠标点击的地方到我们 canvas 的外部取一条线段。然后我们计算它与我们形状的边缘相交的次数。如果交叉点的数量是奇数,那么我们就是这个形状。然后我们对每个形状都这样做,直到找到鼠标所在的位置。
要计算两条线是否相交,我们可以使用 this GeeksforGeeks 文章中的数学。它通过比较线中点的方向来工作。
我在下面有一个例子。
let globalPoints = [
{"x":155,"y":395,"n":"A"}/*0*/,
{"x":235,"y":630,"n":"B"}/*1*/,
{"x":0,"y":630,"n":"C"}/*2*/,
{"x":0,"y":380,"n":"D"}/*3*/,
{"x":0,"y":80,"n":"E"}/*4*/,
{"x":405,"y":80,"n":"F"}/*5*/,
{"x":385,"y":355,"n":"G"}/*6*/,
{"x":224,"y":598,"n":"H"}/*7*/,
{"x":755,"y":520,"n":"I"}/*8*/,
{"x":795,"y":80,"n":"J"}/*9*/,
{"x":890,"y":85,"n":"K"}/*10*/,
{"x":890,"y":630,"n":"L"}/*11*/,
{"x":855,"y":630,"n":"M"}/*12*/,
{"x":0,"y":0,"n":"N"}/*13*/,
{"x":890,"y":0,"n":"O"}/*14*/
];
let shapePoints = [
[0,1,2,3],
[3,4,5,6,0],
[0,7,8,9,5,6],
[9,10,11,12,8],
[9,10,11,12,8],
[7,1,12,8],
[4,13,14,10,9,5]
];
let lastClick = null;
class Shape {
constructor(points)
{
this.points = points;
this.filledIn = false;
}
draw(ctx) {
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.lineWidth = 3;
ctx.fillStyle = 'red';
ctx.moveTo(globalPoints[this.points[0]].x,globalPoints[this.points[0]].y);
for (let i =1;i < this.points.length;i++) {
ctx.lineTo(globalPoints[this.points[i]].x,globalPoints[this.points[i]].y);
}
ctx.closePath();
if (this.filledIn) {
ctx.fill();
}
ctx.stroke();
}
toString()
{
let name = "";
for (let i=0;i<this.points.length;i++) {
name += globalPoints[this.points[i]].n;
}
return name;
}
//use the 'alternate method' from this answer
//https://math.stackexchange.com/questions/926559/when-is-a-point-in-the-plane-inside-a-simple-closed-path
calculateMouseClick(mouseX,mouseY)
{
//generate a ray that extends to outside of the shape,
//we know -1,-1 will be outside as all points are positive
let ray = {"p1":{"x":mouseX,"y":mouseY},"p2":{"x":-1,"y":-1}};
let numIntersections = 0;
//calculate all but last lines
for (let i=0;i<this.points.length-1;i++) {
let shapeEdge = {
"p1":{
"x":globalPoints[this.points[i]].x,
"y":globalPoints[this.points[i]].y
},
"p2":{
"x":globalPoints[this.points[i+1]].x,
"y":globalPoints[this.points[i+1]].y
}
};
if (doIntersect(ray.p1,ray.p2,shapeEdge.p1,shapeEdge.p2)) {
numIntersections++;
}
}
//last line wraps around to start of array
let shapeEdge = {
"p1":{
"x":globalPoints[this.points[0]].x,
"y":globalPoints[this.points[0]].y
},
"p2":{
"x":globalPoints[this.points[this.points.length-1]].x,
"y":globalPoints[this.points[this.points.length-1]].y
}
};
if (doIntersect(ray.p1,ray.p2,shapeEdge.p1,shapeEdge.p2)) {
numIntersections++;
}
if (numIntersections == 0 || numIntersections%2 == 0) {
return false;
}
return true;
}
}
let shapes = [];
function init()
{
"use strict";
for (let i = 0;i< globalPoints.length;i++) {
//add 50 to x and y so we don't draw at the very edge of the canvas
globalPoints[i].x += 50;
globalPoints[i].y += 80;
}
for (let i = 0;i < shapePoints.length;i++) {
shapes.push(new Shape(shapePoints[i]));
}
draw();
let canvas = document.getElementById("drawingArea");
canvas.addEventListener("click",canvasClicked);
}
function draw()
{
let canvas = document.getElementById("drawingArea");
let ctx = canvas.getContext('2d');
ctx.clearRect(0,0,canvas.width,canvas.height);
drawShapes(ctx);
drawPoints(ctx);
drawClick(ctx);
}
function drawShapes(ctx)
{
"use strict";
for (let i = 0;i<shapes.length;i++) {
shapes[i].draw(ctx);
}
}
function drawPoints(ctx)
{
"use strict";
ctx.font = "48px sans";
for (let i = 0;i < globalPoints.length;i++) {
ctx.fillStyle = "black";
ctx.beginPath();
ctx.arc(globalPoints[i].x,globalPoints[i].y,5,0,2*Math.PI);
ctx.fill();
ctx.fillStyle = "#444444";
ctx.fillText(globalPoints[i].n,globalPoints[i].x-40,globalPoints[i].y-25);
}
}
function drawClick(ctx)
{
"use strict";
if (lastClick == null) {
return;
}
ctx.strokeStyle = "#33dd33";
ctx.arc(lastClick.x,lastClick.y,10,0,2*Math.PI);
ctx.fill();
}
function canvasClicked(event)
{
"use strict";
let mouseX = event.offsetX;
let mouseY = event.offsetY;
lastClick = {"x":mouseX,"y":mouseY};
//clear out previous click
for (let i=0;i<shapes.length;i++) {
shapes[i].filledIn = false;
}
let foundShape = false;
let shapeNameElem = document.getElementById("shapeName");
for (let i=0;i<shapes.length;i++) {
let isIn = shapes[i].calculateMouseClick(mouseX,mouseY);
if (isIn) {
shapes[i].filledIn = true;
foundShape = true;
shapeNameElem.innerHTML = "Clicked shape: "+shapes[i].toString();
break;
}
}
if (!foundShape) {
shapeNameElem.innerHTML="";
}
draw();
}
/********************************
onSegment(), orientation(), doIntersect()
https://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/
license CC BY-SA
https://www.geeksforgeeks.org/copyright-information/
********************************/
// Given three colinear points p, q, r, the function checks if
// point q lies on line segment 'pr'
function onSegment(p, q, r)
{
if (q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) &&
q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y))
return true;
return false;
}
// To find orientation of ordered triplet (p, q, r).
// The function returns following values
// 0 --> p, q and r are colinear
// 1 --> Clockwise
// 2 --> Counterclockwise
function orientation(p, q, r)
{
// See https://www.geeksforgeeks.org/orientation-3-ordered-points/
// for details of below formula.
let val = (q.y - p.y) * (r.x - q.x) -
(q.x - p.x) * (r.y - q.y);
if (val == 0) return 0; // colinear
return (val > 0)? 1: 2; // clock or counterclock wise
}
// The main function that returns true if line segment 'p1q1'
// and 'p2q2' intersect.
function doIntersect(p1, q1, p2, q2)
{
// Find the four orientations needed for general and
// special cases
let o1 = orientation(p1, q1, p2);
let o2 = orientation(p1, q1, q2);
let o3 = orientation(p2, q2, p1);
let o4 = orientation(p2, q2, q1);
// General case
if (o1 != o2 && o3 != o4)
return true;
// Special Cases
// p1, q1 and p2 are colinear and p2 lies on segment p1q1
if (o1 == 0 && onSegment(p1, p2, q1)) return true;
// p1, q1 and q2 are colinear and q2 lies on segment p1q1
if (o2 == 0 && onSegment(p1, q2, q1)) return true;
// p2, q2 and p1 are colinear and p1 lies on segment p2q2
if (o3 == 0 && onSegment(p2, p1, q2)) return true;
// p2, q2 and q1 are colinear and q1 lies on segment p2q2
if (o4 == 0 && onSegment(p2, q1, q2)) return true;
return false; // Doesn't fall in any of the above cases
}
window.addEventListener("load",init);
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<script src="test.js"></script>
<link rel="stylesheet" href="test.css"/>
</head>
<body>
<p id="shapeName"></p>
<canvas id="drawingArea" width="1000" height="800"></canvas>
</body>
</html>
没有图像中的点列表似乎是不可能的,我找到了另一个解决方案:(