JSFiddle - React, Tailwind, and code Playground
by mnk
HTML
<div class="container">
<div class="widget round">1</div>
<div class="widget widget-2x2">2</div>
<div class="widget">3</div>
<div class="widget widget-1x2">4</div>
<div class="widget widget-3x1">5</div>
<div class="widget widget-1x2">6</div>
<div class="widget round">7</div>
<div class="widget widget-3x2">8</div>
<div class="widget">9</div>
<div class="widget widget-1x2">10</div>
</div>
CSS
body {
margin: 0;
padding: 0;
}
.container {
background-color: #aaa;
box-sizing: border-box;
padding: 20px;
width: 100vw;
height: 100vh;
display: grid;
gap: 10px;
grid-template-columns: repeat(auto-fit, 120px);
grid-auto-rows: 120px;
grid-auto-flow: dense;
}
.widget {
background-color: #FFF;
border-radius: 5px;
display: flex;
justify-content: center;
align-items: center;
font-size: 2em;
}
.round {
border-radius: 100%;
}
.widget-1x2 {
grid-column: span 2;
}
.widget-3x1 {
grid-column: span 3;
}
.widget-3x2 {
grid-column: span 3;
grid-row: span 2;
}
.widget-2x2 {
grid-column: span 2;
grid-row: span 2;
}
.widget.phantom {
opacity: 0.3;
}
JavaScript
const container = document.querySelector('.container');
const widgets = container.querySelectorAll('.widget');
let dragSrc;
let lastTarget;
let mouseX = 0;
let mouseY = 0;
const onMouseMove = (e) => {
mouseX = e.clientX;
mouseY = e.clientY;
}
document.addEventListener('drag', onMouseMove)
const onDragStart = (e) => {
dragSrc = e.target;
lastTarget = null;
e.target.classList.add('phantom');
e.dataTransfer.setData('text/html', dragSrc.innerHTML);
}
const onDragEnd = (e) => {
dragSrc = null;
e.target.classList.remove('phantom');
}
const onDragEnter = (e) => {
e.preventDefault();
}
const onDragLeave = (e) => {}
const isCursorOnLeftHalfOf = (target) => {
const rect = target.getBoundingClientRect();
const x = mouseX - rect.left;
return x < rect.width / 2;
}
const onDragOver = (e) => {
e.preventDefault();
if(isCursorOnLeftHalfOf(e.target)) {
container.insertBefore(dragSrc, e.target);
} else {
if(e.target.nextSibling) {
container.insertBefore(dragSrc, e.target.nextSibling);
} else {
container.append(dragSrc)
}
}
return false;
}
const onDrop = (e) => {
e.stopPropagation();
e.preventDefault();
e.target.classList.remove('phantom');
return false;
}
const addListeners = (el) => {
el.addEventListener('dragstart', onDragStart.bind(this));
el.addEventListener('dragover', onDragOver.bind(this));
el.addEventListener('dragenter', onDragEnter.bind(this));
el.addEventListener('dragleave', onDragLeave.bind(this));
el.addEventListener('dragend', onDragEnd.bind(this));
el.addEventListener('drop', onDrop.bind(this));
}
const createPhantomEl = (size) => {
const p = document.createElement('div')
p.classList.add('widget');
p.classList.add('phantom');
if(size) {
p.classList.add(size);
}
p.addEventListener('drop', onDrop.bind(this))
return p;
}
widgets.forEach(widget => {
widget.setAttribute('draggable', 'true')
addListeners(widget);
})