Суть такой сортировки: запомнить, какой элемент (источник) начали перетаскивать, затем, когда закончили перетаскивание над каким-то элементом (цель), поместить источник рядом с целью. Всё остальное является визуальным эффектом.
Вот пример...
<table>
<thead>
<tr>
<td><input type="checkbox"></td>
<td>Title</td>
</tr>
</thead>
<tbody>
<tr>
<td><input type="checkbox"></td>
<td>He’s Back: Steve Jobs Adds Rock ana Roll Flair</td>
</tr>
<tr>
<td><input type="checkbox"></td>
<td>TDGU Gadget Wishlist</td>
</tr>
<tr>
<td><input type="checkbox"></td>
<td>Right Track</td>
</tr>
<tr>
<td><input type="checkbox"></td>
<td>3D TV: welcome to the third dimension</td>
</tr>
<tr>
<td><input type="checkbox"></td>
<td>But who has any right to find fault with a man</td>
</tr>
</tbody>
<tfoot>
<tr>
<td><input type="checkbox"></td>
<td>Title</td>
</tr></tfoot>
</table>
<style>
table {
border-collapse: collapse;
border-spacing: 0;
box-shadow: 0 0 1px rgba(0, 0, 0, 0.5);
}
thead { box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.1); }
tfoot { box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.1); }
tbody > tr:nth-child(even) { background-color: white; }
tbody > tr:nth-child(odd) { background-color: #f5f5f5; }
tbody > tr {
-webkit-user-drag: element;
-webkit-user-select: none;
user-select: none;
cursor: move;
}
tbody > tr.over-effect {
background-color: #E3F2FD;
filter: drop-shadow(0 -2px 0 #2196f3);
z-index: 1;
position: relative;
}
tbody > tr.source-effect ~ tr.over-effect {
filter: drop-shadow(0 2px 0 #2196f3);
}
tbody > tr.source-effect {
background-color: #2196f3;
color: white;
filter: none;
}
td { padding: .5em; margin: 1px; }
</style>
<script>
var source, over;
addEventListener("dragstart", event => {
event.dataTransfer.effectAllowed = "move";
event.dataTransfer.setData("text/plain", "");
source = event.target.closest("tbody > tr");
if(source) setTimeout(() => source.classList.add("source-effect"));
});
//addEventListener("dragenter", event => {});
addEventListener("dragover", event => {
event.preventDefault();
event.dataTransfer.dropEffect = "move";
if(over) over.classList.remove("over-effect");
over = event.target.closest("tbody > tr");
if(over) over.classList.add("over-effect");
});
addEventListener("dragleave", event => {
if(over) over.classList.remove("over-effect");
});
addEventListener("dragend", event => {
if(over) over.classList.remove("over-effect");
if(source) source.classList.remove("source-effect");
});
addEventListener("drop", event => {
var destination = event.target.closest("tbody > tr");
if(!destination || destination == source) return;
var isDestinationAmongNextSiblings = false, node = source;
while(node = node.nextElementSibling) {
if(node === destination) {
isDestinationAmongNextSiblings = true;
break;
}
}
destination.parentNode.insertBefore(source, isDestinationAmongNextSiblings ? destination.nextElementSibling : destination);
});
for(const row of document.querySelectorAll("tbody > tr")) {
row.draggable = true;
}
Text.prototype.closest = function(selector) {
return this.parentNode.closest(selector);
}
</script>