提问人:Edd Chang 提问时间:11/17/2023 最后编辑:Edd Chang 更新时间:11/22/2023 访问量:80
连接两个 div 的 SVG 贝塞尔路径
SVG Bezier path connecting two divs
问:
我有两个 div,这两个 div 的边框上有一个图标。我想使用 SVG 路径线进行连接。icons
我尝试了一些解决方案,但由于某种原因,我似乎无法让它看起来不错。该行从实际的 div 开始。我相信我在计算过程中遇到了一些大问题。
我的项目在 Vue 中,所以我无法提供适当的链接来显示复制,但是,这是我正在使用的功能:
function drawConnector() {
if (divA.value && divB.value && svg.value) {
const paths = svg.value
let oldPaths = paths.children
for (let a = oldPaths.length - 1; a >= 0; a--) {
paths.removeChild(oldPaths[a])
}
let x1, y1, x4, y4, dx, x2, x3, path, boxA, boxB
const bezierWeight = 0.8
for (let a = 0; a < 1; a++) {
// divA and divB are the icons
boxA = divA.value
boxB = divB.value
x1 = getOffset(boxA).left + boxA.getBoundingClientRect().width / 2
y1 = getOffset(boxA).top + (boxA.getBoundingClientRect().height - 100) / 2 // EDIT -100
x4 = getOffset(boxB).left + boxB.getBoundingClientRect().width / 2
y4 = getOffset(boxB).top + (boxB.getBoundingClientRect().height - 100)/ 2 // EDIT -100
// Apply padding
dx = Math.abs(x4 - x1) * bezierWeight
if (x4 < x1) {
x2 = x1 - dx
x3 = x4 + dx
} else {
x2 = x1 + dx
x3 = x4 - dx
}
const data = `M${x1} ${y1} C ${x2} ${y1} ${x3} ${y4} ${x4} ${y4}`
path = document.createElementNS(SVG_URL, 'path')
path.setAttribute('d', data)
path.setAttribute('class', 'path')
paths.appendChild(path)
}
}
}
function getOffset(element: HTMLDivElement) {
if (!element.getClientRects().length) {
return { top: 0, left: 0 }
}
let rect = element.getBoundingClientRect()
let win = element.ownerDocument.defaultView
return {
top: rect.top + (win ? win.scrollY : 0),
left: rect.left + (win ? win.scrollX : 0)
}
}
最终结果如下所示:
div 本身是可拖动的,并且会相应地重新绘制线,但它会保持与应该开始的端口的距离。如何确保连接从“输出端口”前面的图标开始,并在另一个输出端口上正确结束。
另外,我应该使用什么样的贝塞尔曲线才能使路径尝试绕过 div 而不是从它下面?
任何指导将不胜感激。
编辑:我一直在篡改它,我在计算 Y 轴时添加了一个。我不知道它为什么有效,我尝试了不同的值,但 100 是效果最好的。-100
我仍然希望得到有关其工作的帮助,因为感觉像是侥幸而不是适当的解决方案。此外,我的目标是让链接不要在它下面,而是在它周围,如果您对此有任何建议,当然将不胜感激,否则我以后可以随时尝试解决它。-100
div
编辑(回答):因此,根据 chrwahl 的帮助,我详细介绍了如何计算我的偏移量。
我最终使用以下代码来解决我的问题。我最终只需要使用相对于父项的偏移量
// calculate position of source port
const sourcePosition = {
x: getOffsetRelativeToParent(divA).left,
y: getOffsetRelativeToParent(divA).top + divA.offsetHeight / 2
}
// calculate position of target port
const targetPosition = {
x: getOffsetRelativeToParent(divB).left + 2,
y: getOffsetRelativeToParent(divB).top + divB.offsetHeight / 2
}
// create svg path based on co-ordinates
const path = document.createElementNS(SVG_URL, 'path')
path.setAttribute(
'd',
`M ${sourcePosition.x}, ${sourcePosition.y} C ${sourcePosition.x + 100}, ${
sourcePosition.y
} ${targetPosition.x - 200}, ${targetPosition.y} ${targetPosition.x}, ${targetPosition.y}`
功能如下:getOffsetRelativeToParent
function getOffsetRelativeToParent(child: HTMLDivElement | HTMLElement) {
const parent = mainDiv.value
if (!parent) {
return { top: 0, left: 0 }
}
const childRect = child.getBoundingClientRect()
const parentRect = parent.getBoundingClientRect()
const top = childRect.top - parentRect.top
const left = childRect.left - parentRect.left
return { top, left }
}
答:
3赞
chrwahl
11/19/2023
#1
很难猜测问题出在图像上,但不知何故,您没有考虑 SVG 相对于该区域的偏移量或位置(这里我称之为板)。
我希望你能从这个例子中学到一些东西:
const board = document.getElementById('board');
const svg = board.querySelector('svg');
const cardProps = {'width': 120, 'height': 80, 'offset': 65};
updatePaths();
function updatePaths() {
let paths = svg.querySelectorAll('path');
[...paths].forEach(path => {
let from = board.querySelectorAll(`div.card`)[path.dataset.from];
let to = board.querySelectorAll(`div.card`)[path.dataset.to];
let fromPoint = {
'left': from.offsetLeft + cardProps.width,
'top': from.offsetTop + cardProps.offset
};
let toPoint = {
'left': to.offsetLeft,
'top': to.offsetTop + cardProps.offset
};
path.setAttribute('d', `M ${fromPoint.left} ${fromPoint.top}
C ${fromPoint.left + 50} ${fromPoint.top} ${toPoint.left - 50} ${toPoint.top} ${toPoint.left} ${toPoint.top}`);
});
}
board.addEventListener('mousedown', e => {
switch (e.target.className) {
case 'card':
e.target.dataset.moving = "on";
e.target.mouse = {
left: e.layerX,
top: e.layerY
};
break;
}
});
board.addEventListener('mousemove', e => {
let card = board.querySelector('div[data-moving="on"]');
if (card) {
card.style.left = `${e.clientX - card.mouse.left - board.offsetLeft}px`;
card.style.top = `${e.clientY - card.mouse.top - board.offsetTop}px`;
updatePaths();
}
});
document.addEventListener('mouseup', e => {
let cards = board.querySelectorAll('div[data-moving]');
[...cards].forEach(card => card.dataset.moving = null);
});
#board {
width: 100%;
height: 400px;
border: solid thin navy;
position: relative;
}
.card {
border: solid thin black;
width: 120px;
height: 80px;
position: absolute;
background-color: WhiteSmoke;
}
.card::before {
content: "";
width: 10px;
height: 10px;
border-radius: 5px;
background-color: gray;
position: absolute;
left: -5px;
top: 60px;
}
.card::after {
content: "";
width: 10px;
height: 10px;
border-radius: 5px;
background-color: gray;
position: absolute;
right: -5px;
top: 60px;
}
svg path {
stroke-width: 2px;
fill: none;
}
<div id="board">
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">
<path d="M 0 0 L 20 20" stroke="black" data-from="0" data-to="1" />
</svg>
<div class="card" style="left: 10px;top: 10px"></div>
<div class="card" style="left: 200px;top: 50px"></div>
</div>
评论