gpt4 book ai didi

javascript - 如何检测和移动/拖动html canvas中的自由绘制线条?

转载 作者:行者123 更新时间:2023-11-29 18:03:12 28 4
gpt4 key购买 nike

我的实现是这样的:


检测mousedown和mousemove是否为真,然后绘制点并将其保存在数组中。
在我的mousemove中,我将转换将绘制的点
我将curPath转换为(Date,value),然后转换为(X and Y-axis),以便将它们保存在画布中的实现中。


我的问题是我将如何检测点[]?这样我就可以突出显示它并拖动它。

最佳答案

更新。

这比我预期的要大。我将继续提高答案的质量。有关状态,请参见答案底部。

采摘。
最简单的方法是检查鼠标与直线上每个点的距离,并突出显示具有最接近点的直线。问题是,当您有多条线和许多点时,它会减慢速度并变得无法使用。

另一种方法是在每行上存储一些额外的信息,以帮助您审核不会被选中的行。在示例中,我为每条线创建一个边界框,然后检查鼠标是在该框内还是附近。如果是这样,那么我将在该行中进行更多搜索,检查每个线段并保持最接近鼠标的线。

一些功能要看。

Helpers.prototype.resetExtent();
Helpers.prototype.extent();
Helpers.prototype.copyOfExtent();


用于查找边界框。 (程度)

function refreshLine(line);


画一条线后调用,它需要绘制一组点并添加边界框(范围)以及演示的其他内容。

function findLineAt(x,y){


此函数使用鼠标的 x,y位置(或其他位置),并返回20像素内最接近的行。它首先检查边界框,如果通过则调用

Helpers.prototype.getDistToPath = function (line,x,y) {


这会将线作为一组点获取,并检查到每条线中心的距离。它还检查支票是否需要更多详细信息并调用该函数。

Helpers.prototype.distPointToLine = function (x, y, x1, y1, x2, y2) {


此功能将返回从点到线的最短距离。 x,y是点, x1,y1,x2,y2是线。它不检查线段,而是检查无限长的线。 Helpers.lineSegPos属性将保留线上最近点的标准化位置。如果需要的话。

因此,返回到 findLineAt(x,y),在所有这些调用之后,它将返回该行(如果找到)或未定义的行。

突出显示并拖动。

突出显示取决于您。我只是很快地循环了行的色调。您可能希望在其周围放置一个边界框。容易做,因为您要做的就是在每次更新时重新绘制最接近的行。

它是如何工作的

主循环。
    update()

处理主循环,每秒调用60次,并且需要部分处理,“绘制”部分用于绘制,并用于拾取。查看if(drawMode ===“ Pick”)。仅在更新中读取鼠标,鼠标侦听器独立设置鼠标。在每个循环结束时,我保存鼠标按钮状态 mouse.lastButton,以便检查鼠标何时上下移动。

pick部分中,如果鼠标未按下,则调用 findLineAt函数。如果找到一行(行!==未定义),则通过更改其颜色并绘制该行来突出显示该行。

因为每个更新都具有当前的mouseButton状态以及上次更新的状态,所以我会知道鼠标按钮第一次向下移动的时间,因为 mouse.button为true而 mouse.lastButton为false。如果鼠标附近有一条线,我将鼠标位置记录在 dragOffXdragOffY中,并将标志 dragging设置为true。我还将画布绘制到另一个画布上以保留背景。此时,我还要检查哪个鼠标按钮按下。如果使用右键,则复制该行并将其设置为要拖动的行,或者如果使用中间按钮,我搜索所有行以在 lineArray中找到其索引并将其删除,然后重新绘制。

下次更新(1/60秒后)且 dragging标志为true mouse.button为true并且 lastLine(最近的线)不是未定义的,我知道我正在拖动线。我清除画布,绘制画布的保存副本(绘制速度更快,然后再次重画所有线条,特别是如果您有100线和1000磅的点),然后重画我拖动的线。

为了锻炼在哪里绘制拖动线,我获得了鼠标距 dragOffXdragOffY的距离,并将 setTransform(1, 0 , 0, 1, mouse.x - dragOffX, mouse.y - dragOffY)的转置部分设置为该距离。这具有将线移动拖动量的效果。我一直这样做,直到鼠标按钮向上。

下降
下次更新,并且mouse.button已启动。
如果鼠标按钮向上并且 dragging标志为true,那么我必须删除该行。此时,获取距 dragOffX dragOffY的鼠标距离并将其添加到行中的每个点。有效地移动路线。我还更新了边界框。然后,我清除屏幕并重新绘制所有线条,这从画布上删除了线条的旧位置,并将其放置在新位置。

做完了

代码长了一点。如果这个答案得到一些支持,那么我将进一步清理它。如果不好,那没关系。

它涵盖了您的问题的基础知识,通过鼠标操作检测和移动点。由点集构成的突出显示线和移动线。我的观点是每个都有x和y的对象数组。每行存储在 lineArray中,一行具有 style, extent, id属性,以及一个包含所有点的名为 line的数组。

有一个鼠标处理程序可以处理必需的鼠标事件。上下移动鼠标,然后将鼠标移出。鼠标移出可以通过关闭鼠标按钮来停止锁定。当鼠标悬停在画布上时,我也会停止上下文菜单。

我使用requestAnimationFrame调用update来使其保持平稳运行。

我希望这可以帮助你。如果您要这样做,我会改善它。如果没有,您将不得不提供更多信息。请问您是否有问题。

更新。
添加了边框,并改进了我昨晚忘记修复的 Helpers.prototype.getDistToPath(line,x,y)。现在,它更快并且不会丢失平行于x和y轴的线。屏幕重绘已移动,以适应边框并添加更多注释。

请不要犹豫,问您是否对此问题有疑问。



function log(){}; // duck up the logs

customCursors ={
encoding:"url('data:image/png;base64,",
drag_small : {
center : " 25 25,",
image : "iVBORw0KGgoAAAANSUhEUgAAADMAAAAzCAYAAAA6oTAqAAACQElEQVRoQ+2azW7DIAyAYZdJW6vlVmmnvcLe/yH2CjtN6i1Tu0m9rIMsJIYChmCvCWkuqZSA/fkPQyoF83VWl5RSqJtQd8kpjnVyB4QdiA0GghhvcHuIBcYH8h9A5DAxEG4gUhgN8rzbiY/9Hs1zjpAjg0nxiEtIDUQCMwWEI+SKYfJBzorDFkvloSvAXKZTs92K9nAoXlTJYFwV9YofunyNAEWHQALjU9qETijpA2OK9CkaHLJ8NYumBrzBoMss/sK6wkyHDLRJyp6EKsxyZUc9Y5R62mzE5/GYvB+hhNFVMVV+EMZVKGeVpoYxwYHp4IUp3VhxwehwjwFdwIQUwawC84oTJgZkwaQogRfIvzcA/DCkb1m63Eu9sE4CFqQBxgty+hLi/mHocnMOVyzFf96EuHv1AkKopmlE27YW5wiuDHD6Vvo8Ds/daOlggh7pYMbBqdaEnon9zpmve9ejDwSS0f3IRBgYGqOwF2W0dysEKWCskO4dkz1vbADMF9PaQ6OF8qBECT1ndZ6pJ2eMa6upZlGg/mFunF91ncGAFtcBxIDmApPVm4WA5gCD6bCO/Qz0EFzMFrvTnLoip3TfKUbJlb+uA41c60S7cPUQS+Ip8syYm2eg9dzjoMFK/edy19KxTqI0j4o9Y5LdVXqxXwFy+zYXfHbfZ9IPKWb85QyrXlh1oqxuxTmDdduJ22sSPUgmgUBV/A8gx0OUoWX1jVhMT3leVW8WKgpcHmFtZ3whxw2iZZIWAF9IOod/rPJ+AQ3iOFgpekFcAAAAAElFTkSuQmCC')"
},
}
function setCursor (name){
if(name === undefined){
canvas.style.cursor = "default";
}
if(customCursors[name] !== undefined){
var cur = customCursors[name];
canvas.style.cursor = customCursors.encoding + cur.image + cur.center + " pointer";
}else{
canvas.style.cursor = name;
}
}
// get canvas button and creat context2D
var canvas = document.getElementById("canV");
var ctx = canvas.getContext("2d");
var but = document.getElementById("redrawAllID");
but.addEventListener("click",function(){
if(drawMode === "Pick"){
drawMode = "Draw";
but.value = "Draw Mode";
}else{
drawMode = "Pick";
but.value = "Pick Mode";
lastLine = undefined;
backGroundImage.ctx.clearRect(0,0,backGroundImage.width,backGroundImage.height);
backGroundImage.ctx.drawImage(canvas,0,0);
}
})
// Groover Bitmaps API dependency replacement
// Extracted from Groover.Bitmaps
var createImage= function(w,h){ // create a image of requier size
var image = document.createElement("canvas");
image.width = w;
image.height =h;
image.ctx = image.getContext("2d"); // tack the context onto the image
return image;
}

var backGroundImage = createImage(canvas.width,canvas.height);
if(!mouse){
// get all the mouse events
canvas.addEventListener('mousemove',mouseMoveEvent);
canvas.addEventListener('mousedown',mouseMoveEvent);
canvas.addEventListener('mouseup' ,mouseMoveEvent);
canvas.addEventListener('mouseout' ,mouseMoveEvent);
canvas.addEventListener("contextmenu", function(e){ e.preventDefault();}, false);

// helper for random colour
var mouse = { // mouse data
x:0,
y:0,
button:false,
lastButton:false, // need this to see when the mouse goes down
which:[false,false,false],
};
}
function mouseMoveEvent(event){// handle all canvas mouse events as they come in
// get new mouse positions
mouse.x = event.offsetX;
mouse.y = event.offsetY;
if(mouse.x === undefined){ // if firefox
mouse.x = event.clientX;
mouse.y = event.clientY;
}
if(event.type === "mouseout"){
mouse.button = false;
mouse.which[0] = false;
mouse.which[1] = false;
mouse.which[2] = false;
}
if(event.type === "mousedown"){ // now see if there is extra info
mouse.button = true;
mouse.which[event.which-1] = true;
}
if(event.type === "mouseup"){ // now see if there is extra info
mouse.button = false;
mouse.which[event.which-1] = false;
}
event.preventDefault();
}

// because forEach is too slow
if (Array.prototype.each === undefined) {
Object.defineProperty(Array.prototype, 'each', {
writable : false,
enumerable : false,
configurable : false,
value : function (func) {
var i,
returned;
var len = this.length;
for (i = 0; i < len; i++) {
returned = func(this[i], i);
if (returned !== undefined) {
this[i] = returned;
}
}
}
});
}
// helper functions
function Helpers(){
}
Helpers.prototype.randomColour = function(){
return "hsl("+Math.floor(Math.random()*360)+",100%,50%)";
}
Helpers.prototype.occilatingColour = function(){
var t = (new Date()).valueOf()
return "hsl("+(Math.floor(t/2)%360)+",100%,50%)";
}

// used for building up the extent of a cloud of points
Helpers.prototype.resetExtent = function(){
if(this.extentObj === undefined){ // check if the extentObj is there
this.extentObj = {}; // if not create it
}
this.extentObj.minX = Infinity;
this.extentObj.minY = Infinity;
this.extentObj.maxX = -Infinity;
this.extentObj.maxY = -Infinity;
}
Helpers.prototype.extent = function( p) { // add a point to the extent
this.extentObj.minX = Math.min(this.extentObj.minX, p.x);
this.extentObj.minY = Math.min(this.extentObj.minY, p.y);
this.extentObj.maxX = Math.max(this.extentObj.maxX, p.x);
this.extentObj.maxY = Math.max(this.extentObj.maxY, p.y);
}
Helpers.prototype.copyOfExtent = function () { // get a copy of the extent object
return {
minX : this.extentObj.minX,
minY : this.extentObj.minY,
maxX : this.extentObj.maxX,
maxY : this.extentObj.maxY,
centerX : (this.extentObj.maxX-this.extentObj.minX)/2,
centerY : (this.extentObj.maxY-this.extentObj.minY)/2,
width:this.extentObj.maxX-this.extentObj.minX,
height:this.extentObj.maxY-this.extentObj.minY,
};
}
Helpers.prototype.getID = function(){ // return a unique ID for this session
if(this.id === undefined){
this.id = 0;
}
this.id += 1;
return this.id;
}
// function to get distance of point to a line
Helpers.prototype.distPointToLine = function (x, y, x1, y1, x2, y2) {
var px = x2 - x1;
var py = y2 - y1;
var u = this.lineSegPos = Math.max(0, Math.min(1, ((x - x1) * px + (y - y1) * py) / (this.distSqr1 = (px * px + py * py))));
return Math.sqrt(Math.pow((x1 + u * px) - x, 2) + Math.pow((y1 + u * py) - y, 2));
}
// function to get the distance of a point to a set of point describing a line
Helpers.prototype.getDistToPath = function (line,x,y) {
var i,len, lineLen,dist;
len = line.length;
x1 = line[0].x;
y1 = line[0].y;
var minDist = Infinity;
for(i = 1; i < len-1; i++){
var near = false;
x2 = line[i].x;
y2 = line[i].y;
lineLen = Math.hypot(x1-x2,y1-y2);
dist = Math.hypot((x1+x2)/2-x,(y1+y2)/2-y);
minDist = Math.min(minDist,dist);
if(dist < lineLen ){
minDist = Math.min(minDist,helpers.distPointToLine(x,y,x1,y1,x2,y2));
}
if(minDist < minDistToPass){
return minDist;
}
x1 = x2;
y1 = y2;
}
return minDist;
}
var helpers = new Helpers();
// Stuff for paths and drawing
var lineArray = []; // list of paths
var lastLine; // last line drawn
var points; // current recording path
var drawing = false; // flag is mouse down and drawing
var dragging = false;
var dragOffX;
var dragOffY;
var drawMode = "Draw";
var minDistToPass = 2; // If a line is closer than this then stop search we found the winning line

// functions to redraw all recorded lines
function redrawAll(){ // style to draw in
ctx.clearRect(0,0,canvas.width,canvas.height);
lineArray.each(function(p){ // draw each one point at atime
redraw(p,p.style);
})
}

// lineDesc is a line and its description
// style is a the style to draw the line in.
// withBox if true draw bounding box [optional]
function redraw(lineDesc,style,withBox){ // draws a single line with style
var line = lineDesc.line;
var len = line.length;
var i;
ctx.beginPath(); //
ctx.strokeStyle = style.colour; // set style and colour
ctx.lineWidth = lineDesc.style.width;
ctx.lineJoin = "round";
ctx.lineCap = "round";

ctx.moveTo(line[0].x,line[0].y); // move to the first pont
for(i = 1; i < line.length; i++){ // lineto all the rest
ctx.lineTo(line[i].x,line[i].y);
};
ctx.stroke(); // stroke
if(withBox){
var w = Math.ceil(lineDesc.style.width/2); // need the lines width to expand the bounding box
ctx.lineWidth = 1;
ctx.strokeStyle = "black";
ctx.strokeRect( // draw the box around the line
lineDesc.extent.minX-w,
lineDesc.extent.minY-w,
lineDesc.extent.width+w*2,
lineDesc.extent.height+w*2
)
}
// done
}

// Finds the closest line and returns it. If no line can be found it returns undefined.
function findLineAt(x,y){
var minDist = 20; // Set the cutoff limit. Lines further than this are ignored
var minLine;
var w;
lineArray.each(function(line){ // do ech line one at a time
w = line.style.width;
if(x >= line.extent.minX-w && x <= line.extent.maxX+w && // is the point inside the bounding
y >= line.extent.minY-w && y <= line.extent.maxY+w){ // boc
var dist = helpers.getDistToPath(line.line,x,y); // if so then do a detailed distance check
if(dist < minDist){ // is the distance to the line less than any other distance found
minDist = dist; // if so remember the line
minLine = line;
}
}
dist = Math.hypot(line.extent.centerX-x,line.extent.centerY-y); // check the distance to the
if(dist<minDist){ // center of the bounding boc
minDist = dist; // use this one if bound box center if close
minLine = line;
}
});
return minLine;

}
function refreshLine(line){ // updates the line to get extend and add stuff if needed
// a good place to smooth the line if need
if(!line.isLine){
var newLine = {}; // new object
newLine.isLine = true; // flag to indicate that the line has been updated
newLine.line = line; // attach the line
newLine.id = helpers.getID(); // get a unique Id for the line
newLine.style = { // give it a style
colour:helpers.randomColour(),
width:Math.random()*4+10,
};
}else{
var newLine = line;
}
var extent = helpers.extent.bind(helpers)
helpers.resetExtent();
line.each(extent);
newLine.extent = helpers.copyOfExtent();
return newLine;
}
function update(){ // one animframe
if(drawMode === "Draw"){
if(!mouse.lastButton && mouse.button ){ // if the mouse but just down;
points = []; // create an new array
drawing = true; // flag drawinf

lineArray.push(points); // save the point array onto the pointsArray
points.push({x:mouse.x,y:mouse.y}); // add the first point
setCursor("none");
}else
if(drawing && mouse.button){ // while the mouse is down keep drawing
points.push({x:mouse.x,y:mouse.y}); // save new point
var p1 = points[points.length-2]; // get last line seg and draw it
var p2 = points[points.length-1];
ctx.lineWidth = 1;
ctx.strokeStyle = "black";
ctx.beginPath();
ctx.moveTo(p1.x,p1.y);
ctx.lineTo(p2.x,p2.y);
ctx.stroke();
}else{
if(drawing){ // if drawing and mouse up
points.push({x:mouse.x,y:mouse.y}); // add the last point
lineArray.push(points = refreshLine(lineArray.pop()))
// redraw the newly draw line
redraw(points,points.style);
drawing = false; // flag that drawing is off.

}else{
setCursor("crosshair");
}
}
}else
if(drawMode = "Pick"){
if(!dragging && !mouse.button){ // is the mouse near a line and not dragging
ctx.clearRect(0,0,canvas.width,canvas.height); // clear background
ctx.drawImage(backGroundImage,0,0); // draw copy of existing lines
var line = findLineAt(mouse.x,mouse.y); // find the line
if(line !== undefined){ // is a line is near
setCursor("drag_small"); // highlight it
lastLine = line; // remember it
// draw it hightlighted with bounding box.
redraw(lastLine,{colour:helpers.occilatingColour(),width:lastLine.width},true);
}else{
setCursor(); // not near a line so turn of cursoe
}
}else // next check if the mouse has jsut been click to start a drag.
if(lastLine !== undefined && !mouse.lastButton && mouse.button){
if(mouse.which[2]){ // Check which button. Right? then copy
var newLine = [];
lastLine.line.each(function(p){newLine.push({x:p.x,y:p.y})});
newLine = refreshLine(newLine)
newLine.style = lastLine.style;
lastLine = newLine;
lineArray.push(newLine)

}else
if(mouse.which[1]){ // Check which button. Middle? then delete
var index;
lineArray.each(function(line,i){
if(line.id === lastLine.id){
index = i;
}
})
if(index !== undefined){
lineArray.splice(index,1);
}
ctx.clearRect(0,0,canvas.width,canvas.height);
redrawAll();
lastLine = undefined;
if(lineArray.length === 0){
drawMode = "Draw";
but.value = "Draw Mode";
}
}
if(lastLine !== undefined){
dragging = true;
dragOffX = mouse.x;
dragOffY = mouse.y;
// backGroundImage.ctx.clearRect(0,0,canvas.width,canvas.height);
// backGroundImage.ctx.drawImage(canvas,0,0);
}
}else{
if(dragging && !mouse.button){ // Drop is dragging true and not mouse down
dragging = false;
var ox = mouse.x-dragOffX; // Find the drag offset
var oy = mouse.y-dragOffY;
helpers.resetExtent(); // get ready for new bounding box.
lastLine.line.each(function(p){ // move each point of the line
p.x += ox;
p.y += oy;
helpers.extent(p); // and test the bounding box
return p;
})
lastLine.extent = helpers.copyOfExtent(); // get the new boundong box
ctx.clearRect(0,0,canvas.width,canvas.height);
redrawAll();
backGroundImage.ctx.clearRect(0,0,backGroundImage.width,backGroundImage.height);
backGroundImage.ctx.drawImage(canvas,0,0);
}else
if(dragging){ // if dragging
ctx.clearRect(0,0,canvas.width,canvas.height); // clear
ctx.drawImage(backGroundImage,0,0); // draw existing lines
var ox = mouse.x-dragOffX; // get the drag offset
var oy = mouse.y-dragOffY;
ctx.setTransform(1,0,0,1,ox,oy); // translate by drag offset
redraw(lastLine,lastLine.style); //draw the dragged line
ctx.setTransform(1,0,0,1,0,0); // reset transform
}
}
}
mouse.lastButton = mouse.button; // set the last button state
window.requestAnimationFrame(update); // request a new frame
}
window.requestAnimationFrame(update)

.canC {
width:256px;
height:256px;
border:black 2px solid;
}
.info{
font-size:x-small;
}

<input type="button" id="redrawAllID" value="Click to Pick"></input>
<div class="info">Mouse down to draw.In pick mode mouse hover over line.<br> Left Button drag,middle delete, right copy.</div>
<canvas class="canC" id="canV" width=256 height=256></canvas>

关于javascript - 如何检测和移动/拖动html canvas中的自由绘制线条?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33473456/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com