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 +...