JSFiddle - React, Tailwind, and code Playground

by frumbert

HTML

<link rel="stylesheet" href="https://unpkg.com/normalize.css">
<script src="https://fonts.googleapis.com/css?family=Nunito+Sans:200,300,400,600,700"></script>
<script src="https://fonts.googleapis.com/css?family=Montserrat:400,700"></script>
<script src="https://unpkg.com/leader-line"></script>
<script src="https://unpkg.com/plain-draggable"></script>
<script src="https://unpkg.com/anim-event"></script>
        <img src="https://images.unsplash.com/photo-1509813685-e7f0e4eaf3ee?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=60">
        <section class="padding">
          <h1>Connect the items</h1>
          <p>Drag connections between these items.</p>
          <div class="flex-row">
            <div class="flex-4">
              <h3>Flavours</h3>
              <div id="plugs" class="boxes">
                <div><span>Grape</span></div>
                <div><span>Vanilla</span></div>
                <div><span>Chocolate, but not that horrible stuff that Cadbury make</span></div>
                <div><img src="https://images.unsplash.com/photo-1534308983496-4fabb1a015ee?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=60"></div>
                <div><span>Lemon</span></div>
                <div><span>Lime like you get in the packets of Starburst snakes</span></div>
              </div>
            </div>
            <div class="flex-2">
              <div id="connectors">
                <div class="connector start"></div>
                <div class="connector end"></div>

                <div class="connector start"></div>
                <div class="connector end"></div>

                <div class="connector start"></div>
                <div class="connector end"></div>

                <div class="connector start"></div>
                <div class="connector end"></div>

                <div class="connector start"></div>
                <div class="connector end"></div>

                <div class="connector...

CSS

:root {
  --text-fg: #222;
  --text-size: 20px;
  --button-bg: #31b7f9;
  --button-bg-light: #a6dffc;
}

html {
  box-sizing: border-box;
  font-size: var(--text-size);
  font-family: -apple-system, BlinkMacSystemFont, "San Francisco", "Helvetica Neue", Helvetica, Ubuntu, Roboto, Noto, "Segoe UI", Arial, sans-serif;
}

*,
*:before,
*:after {
  box-sizing: inherit;
}

* {
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-tap-highlight-color: transparent;
}

body,
html {
  margin: 0;
  padding: 0;
  border: none;
  height: 100%;
}

body {
  letter-spacing: .01rem;
  line-height: 1.8;
  font-size: 1rem;
  font-weight: 400;
  font-family: "Nunito Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
  letter-spacing: .01rem;
  text-shadow: 1px 1px 1px rgba(0, 0, 0, .004);
  color: var(--text-fg);
  min-height: 100vh;
  margin-left: 8rem;
  background-color: #f8f8f8;
}

h1,
h2,
h3,
h4,
h5,
h6 {
  font-family: Montserrat, sans-serif;
  font-weight: 300;
  line-height: 1.2;
  margin: 0
}

h1 {
  font-size: 3rem;
  letter-spacing: .025rem
}

h2 {
  font-size: 2.5rem
}

h3 {
  font-size: 2rem
}

h4 {
  font-size: 1.75rem
}

h5 {
  font-size: 1.5rem
}

h6 {
  font-size: 1.25rem
}

body>img {
  width: 8rem;
  height: 100vh;
  position: fixed;
  top: 0;
  left: 0;
  object-fit: cover;
  object-position: right top;
}

button {
  font-family: Montserrat, sans-serif;
  font-weight: 300;
  line-height: 1.2;
  padding: .75rem 1.25rem;
  margin: 0;
  background-color: white;
  border: none;
  border-radius: .25rem;
  cursor: pointer;
  background-color: var(--button-bg);
  color: white;
  transition: background-color .3s, color .3s;
  box-shadow: 0 0 1rem rgba(0, 0, 0, .1);
}

button:hover {
  background-color: var(--button-bg-light);
}

.flex-row {
  display: flex;
  flex-direction: row;
}

.flex-4 {
 ...

JavaScript

const computedStyle = getComputedStyle(document.documentElement);

const connectorsNode = document.getElementById("connectors");
const connectors = connectorsNode.querySelectorAll(".connector.start");
const lineWrapper = document.getElementById("line-wrapper");


const plugs = document.getElementById("plugs");
const plugPosition = plugs.getBoundingClientRect().y - plugs.previousElementSibling.getBoundingClientRect().height; // div position - header height

const sockets = document.getElementById("sockets");
const socketPosition = sockets.getBoundingClientRect().y - sockets.previousElementSibling.getBoundingClientRect().height; // looks wrong but we are looking for a negaitve offset

function remToPx(rem) {
  return rem * parseFloat(getComputedStyle(document.documentElement).fontSize);
}

function wrapperPosition() {
  lineWrapper.style.transform = 'none';
  const rect = lineWrapper.getBoundingClientRect();
  lineWrapper.style.transform = 'translate(' +
    (-(rect.left + pageXOffset)) + 'px, ' +
    (-(rect.top + pageYOffset)) + 'px)';
}

function init() {
  wrapperPosition();
  let ends = [],
    snaptargets = [];
  Array.from(connectors).forEach(function(start, index) {

    // connectors come in pairs
    const end = start.nextElementSibling;

    const plug = plugs.children[index];
    const p_position = plug.getBoundingClientRect();
    const p_midpoint = (p_position.y - plugPosition) + (p_position.height / 2);
    start.style.top = "calc(" + p_midpoint + "px - .5rem)";

    const socket = sockets.children[index];
    const s_position = socket.getBoundingClientRect();
    const s_midpoint = (s_position.y - socketPosition) + (s_position.height / 2);
    end.style.top = "calc(" + s_midpoint + "px - .5rem)";

    // console.dir(s_midpoint);

    const snaptarget = {
      x: (connectorsNode.offsetWidth - remToPx(.75)),
      y: s_midpoint
    }

    const div = document.createElement("div");
    div.classList.add("hint");
    div.style.top = snaptarget.y +...