Como criar flechas com começo e fim em SVG
Achei isso na prática quando precisei de diagramas técnicos para documentação de API. O problema é que a maioria dos tutoriais mostra apenas setas simples sem controle real de origem e destino. Eu queria que a linha se conectasse precisamente ao centro de cada elemento, não a uma borda qualquer.
flecha com começo e fim
O SVG tem marcadores de seta nativos. Você define o caminho e aplica um marker-end. Mas o controle de começo e fim exige cálculo de interseção, senão a seta fica flutuando ou penetrando o objeto. Primeiro, a estrutura básica do SVG com marcador:
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="600">
<defs>
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#333"/>
</marker>
</defs>
<line x1="100" y1="100" x2="700" y2="500" stroke="#333" stroke-width="2" marker-end="url(#arrowhead)"/>
</svg> O truque do refX="9" é importante. Se você colocar refX="0", a ponta da seta vai na coordenada final. Com refX="9", ela recua 9 unidades, o que permite alinhar com bordas de retângulos.
👉 Clique no botão abaixo para saber mais sobre o assunto!
Para conectar no centro de elementos específicos, use JavaScript para calcular as posições: function connectElements(fromEl, toEl, svg) {
const fromRect = fromEl.getBoundingClientRect();
const toRect = toEl.getBoundingClientRect();
const svgRect = svg.getBoundingClientRect();
const x1 = fromRect.left + fromRect.width / 2 - svgRect.left;
const y1 = fromRect.top + fromRect.height / 2 - svgRect.top;
const x2 = toRect.left + toRect.width / 2 - svgRect.left;
const y2 = toRect.top + toRect.height / 2 - svgRect.top;
const line = document.createElementNS("http://www.w3.org/2000/svg", "line");
line.setAttribute("x1", x1);
line.setAttribute("y1", y1);
line.setAttribute("x2", x2);
line.setAttribute("y2", y2);
line.setAttribute("stroke", "#333");
line.setAttribute("stroke-width", "2");
line.setAttribute("marker-end", "url(#arrowhead)");
svg.appendChild(line);
}
Eu tinha um projeto onde os elementos podiam se mover com drag-and-drop. A solução foi ouvir o evento drag e reconectar a linha a cada update. Funciona, mas tem um problema: se o elemento de destino for muito grande, a seta invade o retângulo. Para corrigir isso, calcule a interseção com as bordas do retângulo alvo:
function getEdgePoint(rect, targetX, targetY) {
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const dx = targetX - centerX;
const dy = targetY - centerY;
const halfWidth = rect.width / 2;
const halfHeight = rect.height / 2;
const slope = Math.abs(dy / dx);
const rectSlope = halfHeight / halfWidth;
let x, y;
if (slope < rectSlope) {
x = dx > 0 ? centerX + halfWidth : centerX - halfWidth;
y = centerY + (dx > 0 ? dy : dy) * (halfWidth / Math.abs(dx));
} else {
y = dy > 0 ? centerY + halfHeight : centerY - halfHeight;
x = centerX + (dy > 0 ? dx : dx) * (halfHeight / Math.abs(dy));
}
return { x, y };
} Essa função retorna o ponto exato na borda do retângulo onde a linha deve terminar. O resultado é visualmente limpo, sem sobreposições estranhas.
Se você precisa de algo mais sofisticado, como curvas de Bézier que contornam outros elementos, considere a biblioteca ELK (Event-driven Layout Kit). Ela resolve routing automático em grafos, mas exige configuração adicional. Para setas simples entre dois pontos fixos, o cálculo manual acima resolve em menos de 50 linhas. Outro detalhe prático: marqueles com `orient="auto"` giram conforme o ângulo da linha. Se quiser uma seta fixa (sem rotação), use `orient="0"` e ajuste manualmente o transform.