skip to Main Content

I want to build on this question which explains how to draw an arrow between two divs using svg, but I want to not have to position the arrow manually between the divs. Regardless of where the divs are on the page I want to be able to get the right side point of div1, the left side point of div2, and draw an arrow between them.

For example, if the html file was

<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" href="styles/styles.css">
</head>
<body>

    <div><p>First div</p></div>
    <div><p>Second div</p></div>

</body>
</html>

and the style file was

div{
    display: inline-block;
    margin-right: 100px;
    border-color: green;
    border-width: 3px;
    border-style: solid;
}

then I would expect something like this:

inline-block example

On the other hand, if the html file was

<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" href="styles/styles.css">
</head>
<body>

    <div><p>First div</p></div>
    <br><br><br>
    <div><p>Second div</p></div>

</body>
</html>

and the style file was

div{
    margin-right: 100px;
    border-color: green;
    border-width: 3px;
    border-style: solid;
}

then I would expect to see this:

static example

This is the code that I’ve worked out so far, based on the comments:

var leftobj = document.getElementById("div1");
var rightobj = document.getElementById("div2");
var leftcoords = leftobj.getBoundingClientRect();
var rightcoords = rightobj.getBoundingClientRect();
var startx = leftcoords.right;
var endx = rightcoords.left;
var starty = (leftcoords.top + leftcoords.bottom) / 2;
var endy = (rightcoords.top + rightcoords.bottom) / 2;
var linepath = `M${startx},${starty} L${endx},${endy}`;
var arr = document.getElementById("arr1");
arr.setAttribute('d', linepath)
<div id="div1"><p>First div</p></div>
<div id="div2"><p>Second div</p></div>
<svg width="300" height="100">
  <defs>
    <marker id="arrow" markerWidth="13" markerHeight="13" refx="2" refy="6" orient="auto">
      <path d="M2,2 L2,11 L10,6 L2,2" style="fill:black;" />
    </marker>
  </defs>
  <path d="M30,150 L100,50" id="arr1"
    style="stroke:black; stroke-width: 1.25px; fill: none;marker-end: url(#arrow);" />
</svg>

But this does not draw the arrow where I want it, like I’ve shown in the pictures. Any help would be super appreciated!

2

Answers


  1. The way to assign the linepath to the SVG is as follows:

        arr.setAttribute('d', linepath)
    
    var leftobj = document.getElementById("div1");
    var rightobj = document.getElementById("div2");
    var leftcoords = leftobj.getBoundingClientRect();
    var rightcoords = rightobj.getBoundingClientRect();
    var startx = leftcoords.right;
    var endx = rightcoords.left;
    var starty = (leftcoords.top + leftcoords.bottom) / 2;
    var endy = (rightcoords.top + rightcoords.bottom) / 2;
    var linepath = `M${startx},${starty} L${endx},${endy}`;
    var arr = document.getElementById("arr1");
    arr.setAttribute('d', linepath)
    #div1 {
      border: solid 2px red;
    }
    
    #div2 {
      border: solid 2px blue;
    }
    
    svg {
      border: solid 2px green;
    }
    <div id="div1">
      <p>First div</p>
    </div>
    <div id="div2">
      <p>Second div</p>
    </div>
    <svg width="300" height="100">
      <defs>
        <marker id="arrow" markerWidth="13" markerHeight="13" refx="2" refy="6" orient="auto">
          <path d="M2,2 L2,11 L10,6 L2,2" style="fill:black;" />
        </marker>
      </defs>
      <path d="M30,150 L100,50" id="arr1"
        style="stroke:black; stroke-width: 1.25px; fill: none;marker-end: url(#arrow);" />
    </svg>
    Login or Signup to reply.
  2. There are several issues that need to be addressed at the same time to achieve what you want.

    First of all, you need to be working in the same coordinate system, i.e. the origin (0,0) must be the same for the divs and the svg. To achieve this, it is necessary to set the margin and padding on all elements to 0 (see the CSS code).

    Next, you need to overlay the svg on top of the divs. This can be achieved with CSS by using the position:fixed property. And then, you need to wrap your divs and the svg in a container div.

    I increased the size of the svg to cover more of the page.

    I added margin-left: 50px; to the divs so that you will be able to see the arrowhead when you experiment with the HTML to test the second layout you present.

    Your JavaScript code seems to be working (however, it is worth avoiding var today, it is better to use let or const).

    One problem with this approach is that the line joins up the points that you want to join, but the arrowhead goes further. You would need to draw a shorter line if you wanted the arrowhead to stop on the exact point (or find another way to achieve this).

    var leftobj = document.getElementById("div1");
    var rightobj = document.getElementById("div2");
    var leftcoords = leftobj.getBoundingClientRect();
    var rightcoords = rightobj.getBoundingClientRect();
    var startx = leftcoords.right;
    var endx = rightcoords.left;
    var starty = (leftcoords.top + leftcoords.bottom) / 2;
    var endy = (rightcoords.top + rightcoords.bottom) / 2;
    var linepath = `M${startx},${starty} L${endx},${endy}`;
    var arr = document.getElementById("arr1");
    arr.setAttribute('d', linepath)
    * {
      margin: 0;
      padding: 0;
    }
    
    #div1,
    #div2 {
      display: inline-block;
      padding: 10px;
      margin-left: 20px;
      margin-right: 100px;
      border-color: green;
      border-width: 3px;
      border-style: solid;
    }
    
    svg {
      position: fixed;
      left: 0px;
      top: 0px;
    }
    
    .container {
      border-width: 0px;
    }
    <div id="container">
      <div id="div1">
        <p>First div</p>
      </div>
      <br>
      <br>
      <div id="div2">
        <p>Second div</p>
      </div>
      <svg width="500" height="500">
      <defs>
        <marker id="arrow" markerWidth="13" markerHeight="13" refx="2" refy="6" orient="auto">
          <path d="M2,2 L2,11 L10,6 L2,2" style="fill:black;" />
        </marker>
      </defs>
      <path d="M30,150 L100,50" id="arr1"
        style="stroke:black; stroke-width: 1.25px; fill: none;marker-end: url(#arrow);" />
    </svg>
    </div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search