Drag/Drop order items
Expanded on https://web.dev/drag-and-drop/#:~:text=To%20make%20an%20object%20draggable,any%20markup%20on%20your%20page.
by PhilQ
HTML
<div id="item-container" class="container">
<div draggable="true" class="box"><div class="box-inner">A</div></div>
<div draggable="true" class="box"><div class="box-inner">B</div></div>
<div draggable="true" class="box"><div class="box-inner">C</div></div>
<div draggable="true" class="box"><div class="box-inner">D</div></div>
<div draggable="true" class="box"><div class="box-inner">E</div></div>
</div>
CSS
*, ::before, ::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
padding: 10px;
}
.container {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
gap: 10px;
padding: 10px 0 0 10px;
}
.box {
position: relative;
display: flex;
align-items: stretch;
flex-shrink: 0;
flex-grow: 0;
flex-basis: calc(25% - 10px);
cursor: move;
}
.box-inner,
.box::before,
.box::after {
pointer-events: none;
display: inline-flex;
width: 100%;
margin: 0;
border: 3px solid #666;
background-color: #ddd;
border-radius: 0.5em;
padding: 10px;
transition: none;
}
.box::before,
.box::after {
width: 0;
background-color: #ddd;
border: 3px dotted #666;
transition: width 0.15s ease, margin 0.15s ease;
}
.box.over.before::before,
.box.over.after::after {
content: '';
width: calc(50% - 5px);
}
.box.over .box-inner {
transition: width 0.15s ease, margin 0.15s ease;
/* border: 3px dotted #666; */
}
.box.over.before .box-inner {
/* right half */
width: calc(50% - 5px);
margin-left: 10px;
}
.box.over.after .box-inner {
/* left half */
width: calc(50% - 5px);
margin-right: 10px;
}
.box.dragging {
opacity: 0.3;
}
.box.over.dragging .box-inner {
border: 3px dotted #666;
}
JavaScript
/**
* Helpers
*/
function getChildIndex(node) {
return node.parentNode ? Array.prototype.indexOf.call(
node.parentNode.childNodes,
node
) : 0;
}
function getChildElementIndex(node) {
return node.parentNode ? Array.prototype.indexOf.call(
node.parentNode.children,
node
) : 0;
}
/**
* Main
*/
function handleDragStart(e) {
items.forEach(function(item) {
item.dataset.i = getChildIndex(item);
});
this.classList.add('dragging');
draggedEl = this;
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData(
'text/plain',
this.dataset.i
);
}
function handleDragEnd(e) {
this.classList.remove('dragging');
document.querySelector('.over').classList.remove('over');
document.querySelector('.before').classList.remove('before');
document.querySelector('.after').classList.remove('after');
}
function handleDragOver(e) {
if (e.preventDefault) {
e.preventDefault();
}
return false;
}
function handleDragEnter(e) {
this.classList.add('over');
if (this.dataset.i < draggedEl.dataset.i) {
this.classList.add('before');
} else if (this.dataset.i > draggedEl.dataset.i) {
this.classList.add('after');
}
}
function handleDragLeave(e) {
this.classList.remove('over', 'before', 'after');
}
function handleDrop(e) {
if (e.preventDefault) {
e.preventDefault();
}
if (this.dataset.i < draggedEl.dataset.i) {
this.before(draggedEl);
} else if (this.dataset.i > draggedEl.dataset.i) {
this.after(draggedEl);
}
return false;
}
let items = null;
let draggedEl = null;
document.addEventListener('DOMContentLoaded', (event) => {
items = document.querySelectorAll('.container .box');
items.forEach(function(item, i) {
item.addEventListener('dragstart', handleDragStart, false);
item.addEventListener('dragover', handleDragOver, false);
item.addEventListener('dragenter', handleDragEnter, false);
item.addEventListener('dragleave', handleDragLeave, false);
item.addEventListener('dragend', handleDragEnd,...