由于这个问题时不时地出现,我已经付出了一些努力。它不是jquery,所以你可能可以在某种程度上简化。仅供参考,这个答案也发布在另一个问题的答案 https://stackoverflow.com/questions/22615338/drawing-lines-between-2-elements-onclick/22662087#22662087,但要求是一样的。使用该问题的html和CSS,这里有一个jsbin演示 http://jsbin.com/guken/3/http://jsbin.com/guken/3/ http://jsbin.com/guken/3/
该方法是创建一个浮动画布元素(粉红色阴影),并将其放置在 DOM 其余部分的下方(使用 z-index)。然后,我计算两个框边界上与框中心之间的线相对应的点。红色和蓝色方块实际上是随行端移动的 div,可用于注释,如源、目标等。
在该 jsbin 中,您可以单击一个元素,然后准备一行以单击下一个元素。它会检测所选元素的悬停,并在将鼠标悬停在其中一个元素上时捕捉到目标。
我不会在此处粘贴所有代码,但我们在客户端 DOM 坐标中从一个 x,y 位置到另一个位置绘制一条线的部分是这样的:
var lineElem;
function drawLineXY(fromXY, toXY) {
if(!lineElem) {
lineElem = document.createElement('canvas');
lineElem.style.position = "absolute";
lineElem.style.zIndex = -100;
document.body.appendChild(lineElem);
}
var leftpoint, rightpoint;
if(fromXY.x < toXY.x) {
leftpoint = fromXY;
rightpoint = toXY;
} else {
leftpoint = toXY;
rightpoint = fromXY;
}
var lineWidthPix = 4;
var gutterPix = 10;
var origin = {x:leftpoint.x-gutterPix,
y:Math.min(fromXY.y, toXY.y)-gutterPix};
lineElem.width = Math.max(rightpoint.x - leftpoint.x, lineWidthPix) +
2.0*gutterPix;
lineElem.height = Math.abs(fromXY.y - toXY.y) + 2.0*gutterPix;
lineElem.style.left = origin.x;
lineElem.style.top = origin.y;
var ctx = lineElem.getContext('2d');
// Use the identity matrix while clearing the canvas
ctx.save();
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, lineElem.width, lineElem.height);
ctx.restore();
ctx.lineWidth = 4;
ctx.strokeStyle = '#09f';
ctx.beginPath();
ctx.moveTo(fromXY.x - origin.x, fromXY.y - origin.y);
ctx.lineTo(toXY.x - origin.x, toXY.y - origin.y);
ctx.stroke();
}
由于示例只有一行,并且我们始终可以存储已“完成”的行以准备创建更多行,因此它使用全局变量lineElem
。第一次尝试绘制线条时,它会创建一个 canvas 元素,将其插入到 DOM 中并将其分配给 lineElem。在此构造之后,它随后重用画布元素,更改大小并重新绘制新的坐标对。
为了防止线条被画布边缘切断,有一个装订线设置可以填充画布的宽度和高度。剩下的就是在客户端 DOM 坐标和在画布本身上绘制的坐标之间进行正确的坐标转换。
唯一不直接的一点是沿着一条线计算框边界上的点的坐标。它并不完美,但这是一个合理的开始。关键是计算目标的角度(to
)从源的角度来看(from
)点,看看它与已知的盒子角角度相比如何:
function getNearestPointOutside(from, to, boxSize) {
// which side does it hit?
// get the angle of to from from.
var theta = Math.atan2(boxSize.y, boxSize.x);
var phi = Math.atan2(to.y - from.y, to.x - from.x);
var nearestPoint = {};
if(Math.abs(phi) < theta) { // crosses +x
nearestPoint.x = from.x + boxSize.x/2.0;
nearestPoint.y = from.y + ((to.x === from.x) ? from.y :
((to.y - from.y)/(to.x - from.x) * boxSize.x/2.0));
} else if(Math.PI-Math.abs(phi) < theta) { // crosses -x
nearestPoint.x = from.x - boxSize.x/2.0;
nearestPoint.y = from.y + ((to.x === from.x) ? from.y :
(-(to.y - from.y)/(to.x - from.x) * boxSize.x/2.0));
} else if(to.y > from.y) { // crosses +y
nearestPoint.y = from.y + boxSize.y/2.0;
nearestPoint.x = from.x + ((to.y === from.y) ? 0 :
((to.x - from.x)/(to.y - from.y) * boxSize.y/2.0));
} else { // crosses -y
nearestPoint.y = from.y - boxSize.y/2.0;
nearestPoint.x = from.x - ((to.y === from.y) ? 0 :
((to.x - from.x)/(to.y - from.y) * boxSize.y/2.0));
}
return nearestPoint;
}
Theta 是到第一个盒子角的角度,phi 是实际的线角度。
要获取客户端坐标中框的位置,您需要使用elem.getBoundingClientRect()
,它产生左、上、宽、高等,我用它来找到盒子的中心:
function getCentreOfElement(el) {
var bounds = el.getBoundingClientRect();
return {x:bounds.left + bounds.width/2.0,
y:bounds.top + bounds.height/2.0};
}
将所有这些放在一起,您可以从一个元素到另一个元素绘制一条线。