JSFiddle - React, Tailwind, and code Playground

by Alexandru Gatea

HTML

<div class="container">
<div id="cpmSvgContainer">
  <svg width="0" height="0">
    <path id="path1" d="M0 0" stroke="black" stroke-width="4px" fill="none"/>
    <path id="path2" d="M0 0" stroke="black" stroke-width="4px" fill="none"/>
    <path id="path3" d="M0 0" stroke="black" stroke-width="4px" fill="none"/>
    <path id="path4" d="M0 0" stroke="black" stroke-width="4px" fill="none"/>
  </svg>
</div>
  <div class="connect-these">
    <div id="res"></div>
    <div id="act"></div>
    <div id="mkt"></div>
    <div id="prod"></div>
  </div>
  <div class="to-this">
    <div id="output"></div>
  </div>
</div>

<!-- zi-mi daca vezi commentu asta -->

SCSS

.container {
  display: flex;
  height: 130vh;
  flex-direction: column;
  position: relative;
  
  .connect-these {
    display: flex;
    width: 420px;
    flex-wrap: wrap;
    position: sticky;
    top: 0;
}

svg {
  overflow: visible;
  width: 100%;
  height: 100%;
}

  .connect-these div {
    width: 100px;
    height: 100px;
    border: 4px solid black;
    margin: 20px;
    /* background: white; */
    border-radius: 10px;
    position: relative;
    
    &::after {
      content: "";
      width: 20px;
      height: 20px;
      /* background: #fff; */
      border-radius: 32px;
      border: 4px solid #000;
      position: absolute;
      top: 100%;
      left: 50%;
      transform: translate(-50%, -50%);
    }
    
    &:nth-child(3) {
      margin-left: 100px;
    }
  }

  .to-this {
    display: flex;
    align-items: flex-end;
    justify-content: flex-end;
    margin-top: 100vh;
    margin-right: 100px;
      
    #output {
      width: 100px;
      height: 100px;
      border: 4px solid black;
      margin: 20px;
      background: white;
      border-radius: 10px;
      position: absolute;
      
      &::after {
        content: "";
        width: 20px;
        height: 20px;
        background: #fff;
        border-radius: 32px;
        border: 4px solid #000;
        position: absolute;
        top: 0%;
        left: 50%;
        transform: translate(-50%, -50%);
      }
    }
  }
}

#svgContainer {
	z-index: -10;
	position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

JavaScript

function drawPath(path, goDown, bezierUp, startX, startY, endX, endY) {
  path.attr("d", "M "  + startX + " " + startY +
    "L " + startX + " " + goDown +
    "C " + startX + " " + (2 *  goDown)  + ", " + endX + " " + (endY - bezierUp) + ", " + endX + " " + (endY - 10) +
    "L " + endX + " " + endY);

}


function connectElements(path, goDown, startElem, endElem) {
	var svgContainer = $("#cpmSvgContainer");
  var svgTop = svgContainer.offset().top;
  var svgLeft = svgContainer.offset().left;
  var startCoord = startElem.offset();
  var endCoord = endElem.offset();
  var startX = startCoord.left + 0.5 * startElem.outerWidth() - svgLeft;
  var startY = startCoord.top + (startElem.outerHeight() / 1.1) - svgTop;
  var endX = endCoord.left + 0.5 * endElem.outerWidth() - svgLeft;
  var endY = endCoord.top - svgTop;
  var goDown = startY + startElem.outerHeight() * goDown;
  var bezierUp = startElem.height() * 2;

  drawPath(path, goDown, bezierUp, startX, startY, endX, endY);

}

function connectAll() {
  // connect all the paths you want!
  connectElements($("#path1"), 2, $("#res"), $("#output"));
  connectElements($("#path2"), 2, $("#act"), $("#output"));
  connectElements($("#path3"), 0.75, $("#mkt"), $("#output"));
  connectElements($("#path4"), 0.5, $("#prod"), $("#output"));

}

$(document).ready(function() {
  $("#svg1").attr("height", "0").attr("width", "0");
  connectAll();
});

$(window).resize(function() {
  $("#svg1").attr("height", "0").attr("width", "0");
  connectAll();
});

$(window).scroll(function() {
  $("#svg1").attr("height", "0").attr("width", "0");
  connectAll();
});