Skip to content

Instantly share code, notes, and snippets.

@uhop
Last active March 14, 2017 20:07
Show Gist options
  • Save uhop/21fa11bf387e234e0d79bf90d7b08735 to your computer and use it in GitHub Desktop.
Save uhop/21fa11bf387e234e0d79bf90d7b08735 to your computer and use it in GitHub Desktop.
The minimalistic DnD code.
(function () {
'use strict';
window.dnd = window.dnd || {};
function Move (container, options, node, e) {
this.container = container;
this.options = options;
this.node = node;
// handle the state
this.node.ownerDocument.body.classList.add('dnd-in-flight');
this.node.classList.add('dnd-dragged');
this.mouseX = e.pageX;
this.mouseY = e.pageY;
this.avatar = (options.makeAvatar || clone)(this);
this.moving = options.moving || moving;
this.over = options.over || noop;
this.handles = [
on(node.ownerDocument, 'mouseup', this.done.bind(this)),
on(node.ownerDocument, 'mousemove', this.move.bind(this)),
on(node.ownerDocument, 'dragstart', stopEvent),
on(node.ownerDocument.body, 'selectstart', stopEvent)
];
(this.options.init || noop)(this);
}
Move.prototype = {
destroy: function () {
this.handles.forEach(function (h) { h.remove(); });
this.handles = [];
// handle the state
this.node.ownerDocument.body.classList.remove('dnd-in-flight');
this.node.classList.remove('dnd-dragged');
if (this.previousOverItem) {
this.previousOverItem.classList.remove('dnd-over');
}
(this.options.destroy || noop)(this);
},
done: function (e) {
stopEvent(e);
(this.options.drop || noop)(this);
this.avatar.parentNode.removeChild(this.avatar);
this.destroy();
},
move: function (e) {
stopEvent(e);
this.moving(this, e);
this.mouseX = e.pageX;
this.mouseY = e.pageY;
this.avatar.style.left = this.x + 'px';
this.avatar.style.top = this.y + 'px';
this.over(this);
}
};
function start (container, options) {
options = options || {};
var callback = process(container, options),
filter = options.filter || '.dnd-handle';
return on(container, 'mousedown', filter, callback);
}
var formNode = {a: 1, input: 1, select: 1, button: 1, textarea: 1, option: 1};
function process (container, options) {
return function (e, node) {
node = node || e.target;
if (!e.button && formNode[node.tagName.toLowerCase()] !== 1 && !node.classList.contains('dnd-ignore')) {
new Move(container, options, node, e);
stopEvent(e);
}
};
}
function clone (mover) {
var node = mover.node,
box = node.getBoundingClientRect(),
style = window.getComputedStyle(node),
avatar = node.cloneNode(true);
mover.x = box.left - parseFloat(style.marginLeft) + window.pageXOffset;
mover.y = box.top - parseFloat(style.marginTop) + window.pageYOffset;
avatar.style.position = 'absolute';
avatar.style.height = style.height;
avatar.style.width = style.width;
avatar.style.left = mover.x + 'px';
avatar.style.top = mover.y + 'px';
avatar.classList.remove('dnd-dragged');
avatar.classList.add(mover.options.avatarClass || 'dnd-avatar');
node.ownerDocument.body.appendChild(avatar);
return avatar;
}
function moving (mover, e) {
mover.x += e.pageX - mover.mouseX;
mover.y += e.pageY - mover.mouseY;
}
function noop (mover) {}
function stopEvent (e) {
e.preventDefault();
e.stopPropagation();
}
dnd.start = start;
}());
<!doctype html>
<html>
<head>
<title>DnD test bed</title>
<script src="../bower_components/on/dist/on.js"></script>
<script src="../lib/dnd.js"></script>
<style>
.container {
font-size: 18pt;
color: black;
display: flex;
flex-direction: column;
max-width: 20em;
margin: 2em;
border: 3px solid #ddd;
padding: 1em;
}
.item {
font-size: 18pt;
color: black;
cursor: pointer;
flex: 1 1 auto;
margin: 0.5em;
border: 3px solid #ccc;
padding: 1em;
}
.item::selection {
background-color: transparent;
}
.dnd-avatar {
background-color: #fcc;
opacity: 0.5;
}
.dnd-dragged {
background-color: #cfc;
opacity: 0.5;
}
.dnd-in-flight {
background-color: #eee;
transition: background-color 0.5s;
}
.dnd-over {
background-color: #ffe;
}
</style>
</head>
<body>
<h1>DnD test bed</h1>
<div class="container">
<div class="item dnd-item dnd-handle">One</div>
<div class="item dnd-item dnd-handle">Two</div>
<div class="item dnd-item dnd-handle">Three</div>
<div class="item dnd-item dnd-handle">Four</div>
<div class="item dnd-item dnd-handle">Five</div>
</div>
<p>Lorem ipsum dolor sit amet, odio consul ea pri, has ridens repudiandae ea, pro ex aliquid accusata. Vix te magna iudicabit, in quot nobis has, mel ex ludus melius ocurreret. Ad homero vidisse quo, soluta possim omittam pri in. Eam adhuc erroribus ex, sed lorem delectus liberavisse ad.</p>
<p>Nisl integre scriptorem te has, eam te iisque denique qualisque. Mei laudem aperiam facilisi ut, id altera tritani recteque nam, est id vitae facilisi evertitur. Democritum repudiandae ea ius. Duo cu tritani invenire, ius illum alterum phaedrum eu, nec officiis interesset ei. Mei elit disputationi ad.</p>
<p>Te quidam labitur abhorreant usu. Habeo deseruisse ut eum, usu ei unum tractatos. Id est ferri movet nostrum, nullam platonem periculis no quo. Falli vivendum at vis, amet possim moderatius et pri. Salutandi instructior mei ut, augue quidam necessitatibus mel id. Mutat primis qui ex, adipisci quaestio eu duo, at sea wisi causae periculis.</p>
<p>Per summo debitis ut. Quem mucius constituam mel ut, eos quodsi animal appetere ex. At doming nostrud vel, inani voluptatum no sed. Et vis exerci vidisse praesent, eam impedit definitionem ea, apeirian vituperatoribus sed no.</p>
<p>Nec augue dolor laoreet ad. Ut eos perfecto nominati. Dolorum appellantur id sed, vis probatus constituam appellantur ea. Cu animal alterum molestie has. Tempor praesent tincidunt eu ius, ei oportere posidonium comprehensam pri.</p>
<script>
dnd.start(document.querySelector('.container'), {
init: function (mover) {
// container box
var rect = mover.container.getBoundingClientRect(),
style = window.getComputedStyle(mover.container),
box = {
top: rect.top + window.pageYOffset + parseFloat(style.borderTopWidth),
bottom: rect.bottom + window.pageYOffset - parseFloat(style.borderBottomWidth) - parseFloat(style.marginBottom)
};
style = window.getComputedStyle(mover.avatar);
box.top -= parseFloat(style.marginTop);
box.bottom -= parseFloat(style.marginTop) + parseFloat(style.borderTopWidth) + parseFloat(style.height) + parseFloat(style.borderBottomWidth);
mover.containerBox = box;
// draggable items
var items = mover.container.querySelectorAll('.dnd-item'), itemBoxes = [];
for (var i = 0; i < items.length; ++i) {
rect = items[i].getBoundingClientRect();
itemBoxes.push({
node: items[i],
top: rect.top + window.pageYOffset,
bottom: rect.bottom + window.pageYOffset
});
}
mover.itemBoxes = itemBoxes;
},
moving: function (mover, e) {
mover.y += e.pageY - mover.mouseY;
mover.y = Math.max(mover.containerBox.top, Math.min(mover.containerBox.bottom, mover.y));
},
over: function (mover) {
// binary search is better, but linear will do in a pinch
this.itemBoxes.some(function (item) {
if (item.top <= mover.mouseY && mover.mouseY < item.bottom) {
if (item.node !== mover.previousOverItem) {
if (mover.previousOverItem) {
mover.previousOverItem.classList.remove('dnd-over');
}
mover.previousOverItem = item.node;
mover.previousOverItem.classList.add('dnd-over');
}
return true;
}
return false;
});
},
drop: function (mover) {
// binary search is better, but linear will do in a pinch
var done = mover.itemBoxes.some(function (item, i, itemBoxes) {
if (mover.mouseY < (item.top + item.bottom) / 2) {
if (item.node !== mover.node) {
mover.container.insertBefore(mover.node, item.node);
}
return true;
}
return false;
});
if (!done) {
mover.container.appendChild(mover.node);
}
}
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment