我可以在画布上画一些线条。 我想让每一行都可以拖动和放置。
但是,它是通过在数组中存储不同行的位置来实现的,我怎样才能使它们中的每一个都像一个可拖动和可拖放的实例?或者我弄错了?
您可以在这里查看代码
var storedLines = []
http://jsfiddle.net/m1erickson/NnZ7a/
非常感谢!
答案 0 :(得分:4)
要制作可拖动的行,您需要将一组端点和一行数组作为端点的索引。
然后,当用户点击并拖动终点或线附近时,您只需按鼠标移动的数量更新终点。
首先创建一个简单的点结构
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});
然后剩下的就是实现鼠标界面和渲染功能。您添加可拖动的点和连接它们的线。如果单击一条线或点附近,则拖动它们。该片段展示了其余部分。
显示正确的用户反馈也很重要。您需要设置光标,工具提示(通过canvas.style.cursor和canvas.title)并突出显示将要生效的对象,以便用户知道将要发生的操作以及他们单击和拖动时的操作。
此外,您应该将鼠标事件设置为文档而不是画布,因为这将捕获鼠标拖动,允许用户在您仍然获取鼠标并移动事件时拖动到画布外。
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);
&#13;
canvas {
position : absolute;
top : 0px;
left : 0px;
}
&#13;
<canvas id="canvas"></canvas>
&#13;