我能够在 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});
然后其余的实现鼠标界面和渲染功能。您添加可拖动的点和连接它们的线。如果单击直线附近或指向点,则将其拖动。片段显示了其余部分。
用户反馈很重要
显示正确的用户反馈也很重要。您需要设置光标,工具提示(通过canvas.style.cursor和canvas.title)并突出显示将要实现的对象,以便用户知道将要执行的操作以及单击和拖动时将执行的操作。
另外,您应该将鼠标事件设置为文档而不是 Canvas ,因为这将捕获鼠标拖动,从而允许用户在仍然获取mouseup和move事件的同时将其拖动到 Canvas 之外。
创建并拖动点和线。
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/