JSFiddle - React, Tailwind, and code Playground
by neonDog
HTML
<div class="container">
<div>
<h3>Sortable List 1</h3>
<ul id="list-1" class="dock sortable" data-sortable="true" data-denies="[3]" data-tag="2">
<li id="item-1" class="draggable-item">Item 01 - <a href="#1">Link 01</a></li>
<li id="item-2" class="draggable-item">Item 02 - <a href="#2">Link 02</a></li>
<li id="item-3" class="draggable-item">Item 03 - <a href="#3">Link 03</a></li>
<li id="item-4" class="draggable-item longer">Item 04 - <a href="#4">Link 04</a></li>
<li id="item-5" class="draggable-item larger">Item 05 - <a href="#5">Link 05</a></li>
<li id="item-6" class="draggable-item">Item 06 - <a href="#6">Link 06</a></li>
</ul>
<button id="sbtn-1" type="button">Serialize</button>
</div>
<div>
<h3>Non-sortable List 2</h3>
<ul id="list-2" class="dock sortable" data-max-items="2" data-can-undock="false" data-accepts="[2]" data-tag="1">
<li id="item-7" class="draggable-item">Item 07 - <a href="#7">Link 07</a></li>
<li id="item-8" class="draggable-item">Item 08 - <a href="#8">Link 08</a></li>
<li id="item-9" class="draggable-item">Item 09 - <a href="#9">Link 09</a></li>
<li id="item-10" class="draggable-item">Item 10 - <a href="#10">Link 10</a></li>
<li id="item-11" class="draggable-item">Item 11 - <a href="#11">Link 11</a></li>
<li id="item-12" class="draggable-item">Item 12 - <a href="#12">Link 12</a></li>
</ul>
<button id="sbtn-2" type="button">Serialize</button>
</div>
</div>
<div class="draggable-item longer">Item 07</div>
<div class="draggable-item longer" style="position:fixed; top:50px; right:50px;">Item 07</div>
<div style="width:50vw; height:100vh; overflow:auto;">
<div>
<h3>Sortable List 3</h3>
<ul id="list-2"...
SCSS
*, *:before, *:after {
box-sizing: border-box;
}
html, body {
height: 100%;
margin:0;
padding:0;
}
body {
overflow: hidden;
overflow-y: scroll;
font-family: Arial, sans-serif;
background: rgb(252,255,244);
background: radial-gradient( ellipse at center, rgba(252,255,244,1) 0%,rgba(223,229,215,1) 50%,rgba(179,190,173,1) 100% );
}
.container {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-around;
width: 100%;
height: 100%;
max-width: 800px;
margin: 0 auto;
div {
flex-basis: 40%;
h3 {
font-weight: bold;
font-size: 150%;
line-height: 1em;
}
}
}
ul.sortable {
display: block;
list-style: none;
background: #e0e0e0;
border-radius: 4px;
box-shadow: 0 0 40px rgba(0,0,0,0.4);
padding: 5px;
margin:0;
& > li {
display: inline-block;
margin: 1px;
padding: 10px;
border-radius: 4px;
cursor: move;
background: #fff;
&.larger {
width:200px;
}
&.longer {
height:100px;
}
&:hover {
background: #ffffe0;
}
&.active {
background: #ccc;
}
&.dragging {
background: #e0ffff;
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
opacity: 0.5;
}
}
}
div.draggable-item {
background:red;
width:100px;
color:white;
padding:5px;
display:inline-block;
}
.draggable-item, .dock {
transition: background-color 400ms ease-out, border-color 400ms ease-out, color 400ms ease-out;
}
.dock {
background: #eeeeee;
&.active {
background: #e0ffff;
}
&.denied {
background: red;
}
}
.dock-bottom {
position:fixed;
bottom:0;
height:60px;
left:0;
right:0;
display: inline-flex;
flex-direction: row-reverse;
justify-content: flex-start;
align-items: flex-end;
}
JavaScript
// get position of mouse/touch in relation to viewport
const _w = window,
_b = document.body,
_d = document.documentElement;
const getPointBad = function(e) {
return {x: (e.pageX || e.clientX || 0), y:e.pageY || e.clientY || 0};
};
const getPoint = function( e ) {
var scrollX = Math.max( 0, _w.pageXOffset || _d.scrollLeft || _b.scrollLeft || 0 ) - ( _d.clientLeft || 0 ),
scrollY = Math.max( 0, _w.pageYOffset || _d.scrollTop || _b.scrollTop || 0 ) - ( _d.clientTop || 0 ),
pointX = e ? ( Math.max( 0, e.pageX || e.clientX || 0 ) - scrollX ) : 0,
pointY = e ? ( Math.max( 0, e.pageY || e.clientY || 0 ) - scrollY ) : 0;
return { x: pointX, y: pointY };
};
const getOffset = function(el) {
const rect = el.getBoundingClientRect(),
scrollLeft = window.pageXOffset || document.documentElement.scrollLeft,
scrollTop = window.pageYOffset || document.documentElement.scrollTop;
if (el)
return { top: rect.top + scrollTop, left: rect.left + scrollLeft }
};
const getAbsoluteOffset = function (el) {
const doc = document,
win = window,
body = doc.body;
// pageXOffset and pageYOffset work everywhere except IE <9.
let offsetX = win.pageXOffset !== undefined ? win.pageXOffset :
(doc.documentElement || body.parentNode || body).scrollLeft,
offsetY = win.pageYOffset !== undefined ? win.pageYOffset :
(doc.documentElement || body.parentNode || body).scrollTop,
rect = el.getBoundingClientRect();
if (el !== body) {
let parent = el.parentNode;
// The element's rect will be affected by the scroll positions of
// *all* of its scrollable parents, not just the window, so we have
// to walk up the tree and collect every scroll offset. Good times.
while (parent !== body) {
offsetX += parent.scrollLeft;
offsetY += parent.scrollTop;
parent = parent.parentNode;
}
}
return {
bottom: rect.bottom + offsetY,
height: rect.height,
left: rect.left + offsetX,
right: rect.right +...