问题
I'm trying to find a way to drag and drop polygon point or line with javascript in html5 canvas by selecting a path points with mouse click on it
Kind like image below
the selected points are moved by clicking and dragging them into new position
var canvas=document.getElementById("canvas");
var context=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
context.lineWidth=2;
context.strokeStyle='blue';
var coordinates = [];
var isDone=false;
$('#done').click(function(){
isDone=true;
});
$("#canvas").mousedown(function(e){handleMouseDown(e);});
function handleMouseDown(e){
if(isDone || coordinates.length>10){return;}
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
coordinates.push({x:mouseX,y:mouseY});
drawPolygon();
}
function drawPolygon(){
context.clearRect(0,0,cw,ch);
context.beginPath();
context.moveTo(coordinates[0].x, coordinates[0].y);
for(index=1; index<coordinates.length;index++) {
context.lineTo(coordinates[index].x, coordinates[index].y);
}
context.closePath();
context.stroke();
}
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h4>Click to assign polygon vertices</h4>
<button id=done>Click when done assigning points</button>
<br><canvas id="canvas" width=300 height=300></canvas>
回答1:
The answer given is an example of bad practice in regard to handling the mouse and rendering content.
Rendering from mouse events will chew power and force canvas renders when not needed. The mouse can fire at rates up to 1000 times a second while the max display rate is only 60 times a second.
Using mouse move events means that many of the rendered updates will not be seen by the user, and are just wasted CPU/GPU cycles at best, at worst a major battery drain.
Always use requestAnimationFrame
to render any content that is frequently changing.
The example below decoples the rendering from input events using requestAnimationFrame to render content only when needed. It also adds some feedback for the user via the cursor and highlighting points.
var ctx = canvas.getContext("2d");
requestAnimationFrame(update)
mouse = {x : 0, y : 0, button : 0, lx : 0, ly : 0, update : true};
function mouseEvents(e){
const bounds = canvas.getBoundingClientRect();
mouse.x = e.pageX - bounds.left - scrollX;
mouse.y = e.pageY - bounds.top - scrollY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
mouse.update = true;
}
["mousedown","mouseup","mousemove"].forEach(name => document.addEventListener(name,mouseEvents));
ctx.lineWidth = 2;
ctx.strokeStyle = "blue";
const point = (x,y) => ({x,y});
const poly = () => ({
points : [],
addPoint(p){ this.points.push(point(p.x,p.y)) },
draw() {
ctx.lineWidth = 2;
ctx.strokeStyle = "blue";
ctx.beginPath();
for (const p of this.points) { ctx.lineTo(p.x,p.y) }
ctx.closePath();
for (const p of this.points) {
ctx.moveTo(p.x + 4,p.y);
ctx.arc(p.x,p.y,4,0,Math.PI *2);
}
ctx.stroke();
},
closest(pos, dist = 8) {
var i = 0, index = -1;
dist *= dist;
for (const p of this.points) {
var x = pos.x - p.x;
var y = pos.y - p.y;
var d2 = x * x + y * y;
if (d2 < dist) {
dist = d2;
index = i;
}
i++;
}
if (index > -1) { return this.points[index] }
}
});
function drawCircle(pos,color="red",size=8){
ctx.strokeStyle = color;
ctx.beginPath();
ctx.arc(pos.x,pos.y,size,0,Math.PI *2);
ctx.stroke();
}
const polygon = poly();
var activePoint,cursor;
var dragging= false;
function update(){
if (mouse.update) {
cursor = "crosshair";
ctx.clearRect(0,0,canvas.width,canvas.height);
if (!dragging) { activePoint = polygon.closest(mouse) }
if (activePoint === undefined && mouse.button) {
polygon.addPoint(mouse);
mouse.button = false;
} else if(activePoint) {
if (mouse.button) {
if(dragging) {
activePoint.x += mouse.x - mouse.lx;
activePoint.y += mouse.y - mouse.ly;
} else { dragging = true }
} else { dragging = false }
}
polygon.draw();
if (activePoint) {
drawCircle(activePoint);
cursor = "move";
}
mouse.lx = mouse.x;
mouse.ly = mouse.y;
canvas.style.cursor = cursor;
mouse.update = false;
}
requestAnimationFrame(update)
}
#canvas{
border:1px
solid black;
}
<canvas id="canvas" width=300 height=300></canvas>
回答2:
I've made a few changes to your code. Since I need to detect the position of the mouse I've added a function to do this.
The logic is as it follows:
you draw the points.
When you are done you can begin to drag the points
If you click inside a point (a small circle around the point) you save the index of the point.
on mouse move you move the point you clicked in.
on mouse up no point is dragged anymore.
I hope this is what you need.
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var cw = canvas.width;
var ch = canvas.height;
var mouse = {};
var draggable = false;
context.lineWidth = 2;
context.strokeStyle = "blue";
var coordinates = [];
var isDone = false;
done.addEventListener("click", function() {
isDone = true;
});
canvas.addEventListener("mousedown", function(e) {
handleMouseDown(e);
});
function handleMouseDown(e) {
mouse = oMousePos(canvas, e);
//if isDone you can drag
if (isDone || coordinates.length > 10) {
for (index = 0; index < coordinates.length; index++) {
// you draw a small circle no stroke, no fill
context.beginPath();
context.arc(
coordinates[index].x,
coordinates[index].y,
5,
0,
2 * Math.PI
);
// if the mouse is inside the circle
if (context.isPointInPath(mouse.x, mouse.y)) {
// you can drag this point
// I'm using index + 1 because index == 0 is false
draggable = index + 1;
// if I have a point a can break the loop
break;
}
}
} else {
coordinates.push({ x: mouse.x, y: mouse.y });
drawPolygon();
}
}
function drawPolygon() {
context.clearRect(0, 0, cw, ch);
context.beginPath();
context.moveTo(coordinates[0].x, coordinates[0].y);
for (index = 1; index < coordinates.length; index++) {
context.lineTo(coordinates[index].x, coordinates[index].y);
}
context.closePath();
context.stroke();
// Additionaly I'm drawing a small circle around every point
// you can delete this.
for (index = 0; index < coordinates.length; index++) {
context.beginPath();
context.arc(coordinates[index].x, coordinates[index].y, 5, 0, 2 * Math.PI);
context.stroke();
}
}
canvas.addEventListener("mousemove", function(e) {
if (isDone) {
if (draggable) {
mouse = oMousePos(canvas, e);
// draggable - 1 is the index of the point in the coordinates array
coordinates[draggable - 1].x = mouse.x;
coordinates[draggable - 1].y = mouse.y;
drawPolygon();
}
}
});
canvas.addEventListener("mouseup", function(e) {
if (draggable) {
draggable = false;
}
});
// a function to detect the mouse position
function oMousePos(canvas, evt) {
var ClientRect = canvas.getBoundingClientRect();
return {
//objeto
x: Math.round(evt.clientX - ClientRect.left),
y: Math.round(evt.clientY - ClientRect.top)
};
}
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<h4>Click to assign polygon vertices</h4>
<button id=done>Click when done assigning points</button>
<br><canvas id="canvas" width=300 height=300></canvas>
来源:https://stackoverflow.com/questions/53427699/editing-selections-of-path-points-or-line