javascript - 使每条 Canvas 线都可拖放

标签 javascript jquery html canvas

我可以在 Canvas 上画一些线。 我想让每一行都可以拖放。

但是,它是通过将不同行的位置存储在数组中来实现的,如何使它们像一个可拖放的实例一样?还是我听错了?

你可以在这里查看代码

var storedLines = []

http://jsfiddle.net/m1erickson/NnZ7a/

非常感谢!

最佳答案

Canvas 可拖动对象。

要制作可拖动的线,您需要保留一个端点数组和一个线数组作为端点的索引。

然后,当用户在端点或线附近单击并拖动时,您只需根据鼠标移动的量更新端点。

一个点,一条线,一些列表

首先创建一个简单的点结构

const Point2 = (x,y) => ({x,y});  // creates a point

然后是一个包含点数、添加点数以及您可能需要的任何其他内容的列表。

const list = {
    items : null,
    add(item) { this.items.push(item); return item },
    eachItem(callback) { 
        var i;
        while(i < this.items.length){
             callback(this.items[i],i);
        }
    }
}

然后您从列表结构创建一个点列表

function createList(extend){
    return Object.assign({},list,{items : []},extend);
}

const points = createList();

直线是一组点索引

const Line = (p1,p2) => ({p1,p2});
const lines = createList();

寻找最近的

要从鼠标坐标中选择一个点,您需要找到离鼠标最近的点。

// this will extend the points list
function getClosestPoint(from ,minDist) {
    var closestPoint;
    this.eachItem(point => {
        const dist = Math.hypot(from.x - point.x, from.y - point.y);
        if(dist < minDist){
            closestPoint = point;
            minDist = dist;
        }
    });
    return closestPoint;
}

这条线也一样,但不是那么简单。您需要一个函数来计算点到线段的距离。

function distanceLineFromPoint(line,point){
    const lx = points.items[line.p1].x;
    const ly = points.items[line.p1].y;
    const v1x = points.items[line.p2].x - lx;
    const v1y = points.items[line.p2].y - ly;
    const v2x = point.x - lx;
    const v2y = point.y - ly;
    // get unit dist of closest point
    const u = (v2x * v1x + v2y * v1y)/(v1y * v1y + v1x * v1x);
    if(u >= 0 && u <= 1){  // is the point on the line
        return Math.hypot(lx + v1x * u - point.x, ly + v1y * u - point.y);
    } else if ( u < 0 ) {  // point is before start
        return Math.hypot(lx - point.x, ly - point.y);
    }
    // point is after end of line
    return Math.hypot(points.items[line.p2].x - point.x, points.items[line.p2].y - point.y);
}

// this will extend the lines list
function getClosestline(from ,minDist) {
    var closestLine;
    this.eachItem(line => {
        const dist = distanceLineFromPoint(line,from);
        if(dist < minDist){
            closestLine = line;
            minDist = dist;
        }
    });
    return closestLine;
}

使用这些函数,我们应该扩展列表对象,以便使用扩展重新创建它们。

 const points = createList({getClosest : getClosestPoint});
 const lines = createList({getClosest : getClosestline});

然后剩下的就是实现鼠标接口(interface)和渲染功能了。您添加可拖动的点和连接它们的线。如果您在一条线或点附近单击,您将拖动它们。该片段显示了其余部分。

用户反馈很重要

显示正确的用户反馈也很重要。您需要设置光标、工具提示(通过 canvas.style.cursor 和 canvas.title)并突出显示将受到影响的对象,以便用户知道将发生什么 Action 以及单击和拖动时会发生什么。

此外,您应该将鼠标事件设置到文档而不是 Canvas ,因为这将捕获鼠标拖动,允许用户在 Canvas 外拖动,同时您仍然获得 mouseup 和 move 事件。

创建和拖动点和线。

const ctx = canvas.getContext("2d");
const Point2 = (x,y) => ({x,y});  // creates a point
const Line = (p1,p2) => ({p1,p2});
const setStyle = (style) => eachOf(Object.keys(style), key => { ctx[key] = style[key] } );
const eachOf = (array, callback) => {var i = 0; while (i < array.length && callback(array[i],i ++) !== true ); };


const list = {
    items : null,
    add(item) { this.items.push(item); return item },
    eachItem(callback) { 
        var i = 0;
        while(i < this.items.length){
             callback(this.items[i],i++);
        }
    }
}
function createList(extend){
    return Object.assign({},list,{items : []},extend);
}
// this will extend the points list
function getClosestPoint(from ,minDist) {
    var closestPoint;
    this.eachItem(point => {
        const dist = Math.hypot(from.x - point.x, from.y - point.y);
        if(dist < minDist){
            closestPoint = point;
            minDist = dist;
        }
    });
    return closestPoint;
}
function distanceLineFromPoint(line,point){
    const lx = points.items[line.p1].x;
    const ly = points.items[line.p1].y;
    const v1x = points.items[line.p2].x - lx;
    const v1y = points.items[line.p2].y - ly;
    const v2x = point.x - lx;
    const v2y = point.y - ly;
    // get unit dist of closest point
    const u = (v2x * v1x + v2y * v1y)/(v1y * v1y + v1x * v1x);
    if(u >= 0 && u <= 1){  // is the point on the line
        return Math.hypot(lx + v1x * u - point.x, ly + v1y * u - point.y);
    } else if ( u < 0 ) {  // point is before start
        return Math.hypot(lx - point.x, ly - point.y);
    }
    // point is after end of line
    return Math.hypot(points.items[line.p2].x - point.x, points.items[line.p2].y - point.y);
}
// this will extend the lines list
function getClosestline(from ,minDist) {
    var closestLine;
    this.eachItem(line => {
        const dist = distanceLineFromPoint(line,from);
        if(dist < minDist){
            closestLine = line;
            minDist = dist;
        }
    });
    return closestLine;
}
function drawPoint(point){
    ctx.moveTo(point.x,point.y);
    ctx.rect(point.x - 2,point.y - 2, 4,4);
}
function drawLine(line){
    ctx.moveTo(points.items[line.p1].x,points.items[line.p1].y);
    ctx.lineTo(points.items[line.p2].x,points.items[line.p2].y);
}
function drawLines(){ this.eachItem(line => drawLine(line)) }
function drawPoints(){this.eachItem(point => drawPoint(point)) }

const points = createList({
  getClosest : getClosestPoint,
  draw : drawPoints,
});
const lines = createList({
  getClosest : getClosestline,
  draw : drawLines,
});
const mouse  = {x : 0, y : 0, button : false, drag : false, dragStart : false, dragEnd : false, dragStartX : 0, dragStartY : 0}
function mouseEvents(e){
	mouse.x = e.pageX;
	mouse.y = e.pageY;
	const lb = mouse.button;
	mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
	if(lb !== mouse.button){
		if(mouse.button){
			mouse.drag = true;
			mouse.dragStart = true;
			mouse.dragStartX = mouse.x;
			mouse.dragStartY = mouse.y;
		}else{
			mouse.drag = false;
			mouse.dragEnd = true;
		}
	}
}
["down","up","move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents));
// short cut vars 
var w = canvas.width;
var h = canvas.height;
var cw = w / 2;  // center 
var ch = h / 2;
var globalTime;
var closestLine;
var closestPoint;
var pointDrag; // true is dragging a point else dragging a line
var dragOffsetX;
var dragOffsetY;
var cursor;
var toolTip;
var helpCount = 0;
const minDist = 20;
const lineStyle = {
  lineWidth : 2,
  strokeStyle : "green",
}
const pointStyle = {
  lineWidth : 1,
  strokeStyle : "blue",
}
const highlightStyle = {
  lineWidth : 3,
  strokeStyle : "red",
}
const font = {
  font : "18px arial",
  fillStyle : "black",
  textAlign : "center",
}


// main update function
function update(timer){
    cursor = "crosshair";
    toolTip = helpCount < 2 ? "Click drag to create a line" : "";
    globalTime = timer;
    ctx.setTransform(1,0,0,1,0,0); // reset transform
    ctx.globalAlpha = 1;           // reset alpha
	if(w !== innerWidth || h !== innerHeight){
		cw = (w = canvas.width = innerWidth) / 2;
		ch = (h = canvas.height = innerHeight) / 2;
	}else{
		ctx.clearRect(0,0,w,h);
	}
  if(mouse.drag=== false){
    closestLine = undefined;
    closestPoint = points.getClosest(mouse,minDist);
    if(closestPoint === undefined){
       closestLine = lines.getClosest(mouse,minDist);
    }
    if(closestPoint || closestLine){
       toolTip = "Click drag to move " + (closestPoint ? "point" : "line");     
       cursor = "move";
    }
  }
  if(mouse.dragStart){
    if(closestPoint){
      dragOffsetX = closestPoint.x - mouse.x;
      dragOffsetY =  closestPoint.y - mouse.y;
      pointDrag = true;
    
    }else if( closestLine){
      dragOffsetX = points.items[closestLine.p1].x - mouse.x;
      dragOffsetY = points.items[closestLine.p1].y - mouse.y;
      pointDrag = false;
    
    } else {
      points.add(Point2(mouse.x,mouse.y));
      closestPoint = points.add(Point2(mouse.x,mouse.y));
      closestLine = lines.add(Line(points.items.length-2,points.items.length-1));
      dragOffsetX = 0;
      dragOffsetY = 0;
      pointDrag = true;
      helpCount += 1;
      
    }
    mouse.dragStart = false;
  
  }else if(mouse.drag){
      cursor = 'none';
      if(pointDrag){
        closestPoint.x = mouse.x + dragOffsetX;
        closestPoint.y = mouse.y + dragOffsetY;
      }else{
        const dx = mouse.x- mouse.dragStartX;
        const dy = mouse.y -mouse.dragStartY;
        mouse.dragStartX = mouse.x;
        mouse.dragStartY = mouse.y;
        points.items[closestLine.p1].x +=  dx;
        points.items[closestLine.p1].y +=  dy;
        points.items[closestLine.p2].x +=  dx;
        points.items[closestLine.p2].y +=  dy;        
      }
  }else{
  
  
  }
  // draw all points and lines
  setStyle(lineStyle);
  ctx.beginPath();
  lines.draw();
  ctx.stroke();
  setStyle(pointStyle);
  ctx.beginPath();
  points.draw();
  ctx.stroke();

  
  // draw highlighted point or line
  setStyle(highlightStyle);
  ctx.beginPath();
  if(closestLine){ drawLine(closestLine) }
  if(closestPoint){ drawPoint(closestPoint) }
  
  ctx.stroke();
      
  
  if(helpCount < 2){
     setStyle(font);
     ctx.fillText(toolTip,cw,30);
  }
  
  
  canvas.style.cursor = cursor;
  if(helpCount < 5){
      canvas.title = toolTip;
  }else{
      canvas.title = "";
  }
  requestAnimationFrame(update);
}
requestAnimationFrame(update);
canvas { 
  position : absolute; 
  top : 0px; 
  left : 0px; 
}
<canvas id="canvas"></canvas>

关于javascript - 使每条 Canvas 线都可拖放,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44923395/

相关文章:

javascript - 在 C# cshtml View 中显示元组列表

jquery - 尽管有内容 div,但使 div 容器高度为 100%

javascript - Canvas.drawImage 不工作

JavaScript/jQuery - offsetLeft 和 offsetTop

javascript - CSS 更改不会反射(reflect)在 Polymer 中

python - 使用 Python 自动转换 '<' 和 '>'

javascript - 使第二个选择菜单依赖于 jQuery UI Slectmenu 中的第一个选择菜单

javascript - 将下载链接从网络浏览器传递到第三方应用程序

javascript - 在文件夹中添加电子表格文件

javascript - JQuery更新html中的内容