Существует масса решений для сортировки списков, например tableDTD, tableSorter, jQuery.sortable и прочих, которые имеют широкий функционал, но при этом — большой код.
Моё решение использует jQuery, имеет компактный код (42 строки без комментариев) и предусматривает перемешивание элементов (относительно позиционированных) в пределах родительского контейнера.
Весь ход мыслей прокомментирован в JavaScript.
Посмотреть в действии на jsFiddle (некомментированный код).
HTML-разметка
<!DOCTYPE html>
<html><head>
<meta charset='utf-8'>
<link type='text/css' rel='stylesheet' href='style.css'>
<script type='text/javascript' src='https://code.jquery.com/jquery-2.0.3.min.js'></script>
<script type='text/javascript' src='actions.js'></script>
</head><body>
<div class="container">
<div class="drop">1</div>
<div class="drop">2</div>
<div class="drop">3</div>
<div class="drop">4</div>
<div class="drop">5</div>
</div>
<input class="focus-out">
</body></html>
style.css
.container {width:200px;}
.drop {position:relative; top:0px; height:16px; cursor:default; z-index:1;}
.focus-out {position:absolute; width:2px; height:2px; left:-10px; top:-10px; background:none;}
actions.js
$(document).ready(function(){
// нажали на любом элементе списка
$('.drop').mousedown(function(e){
var drop = $(this);
// верхняя граница контейнера
var posParentTop = drop.parent().position().top;
// нижняя граница контейнера
var posParentBottom = posParentTop + drop.parent().height();
// координаты исходного положения элемента
var posOld = drop.position().top;
// коррекция относительно позиции курсора при нажатии
var posOldCorrection = e.pageY - posOld;
// поднимаем нажатый элемент по z-оси
drop.css({'z-index':2, 'background-color':'#eeeeee'});
// перетягиваем элемент
var mouseMove = function(e){
// получаем новые динамические координаты элемента
var posNew = e.pageY - posOldCorrection;
// если элемент перетянут выше верхней границы контейнера
if (posNew < posParentTop){
// устанавливаем позицию элемента, равную позиции родителя
drop.offset({'top': posParentTop});
// отменяем выделение, переместив фокус в спрятанный (но не скрытый через css) input
$('.focus-out').focus();
// если элемент перетянут ниже нижней границы контейнера
} else if ((posNew + drop.height()) > posParentBottom){
// устанавливаем позицию элемента, равную позиции родителя + высоте родителя - высоте элемента
drop.offset({'top': posParentBottom - drop.height()});
// отменяем выделение
$('.focus-out').focus();
// если элемент в пределах контейнера
} else {
// устанавливаем новую высоту (элемент перемещается за курсором)
drop.offset({'top': posNew});
// если элемент перемещен вверх на собственную высоту
if (posOld - posNew > drop.height() - 2){
// меняем элемент с предыдущим в DOM
drop.insertBefore(drop.prev());
// обнуляем позицию
drop.css({'top':0});
// снова получаем координаты исходного и текущего положения
posOld = drop.position().top;
posNew = e.pageY - posOldCorrection;
posOldCorrection = e.pageY - posOld;
// если элемент перемещен вниз на собственную высоту
} else if (posNew - posOld > drop.height() - 2){
// меняем элемент со следующим в DOM
drop.insertAfter(drop.next());
drop.css({'top':0});
posOld = drop.position().top;
posNew = e.pageY - posOldCorrection;
posOldCorrection = e.pageY - posOld;
}
// отменяем выделение
$('.focus-out').focus();
}
};
// отпускаем клавишу мыши
var mouseUp = function(){
// завершаем выполнение функции
$(document).off('mousemove', mouseMove).off('mouseup', mouseUp);
// плавно возвращаем наш элемент на ближайшее освободившееся место
drop.animate({'top':0}, 100);
// возвращаем z-позицию на уровень остальных элементов
setTimeout(function(){
drop.css({'z-index':1, 'background-color':'transparent'});
}, 100);
};
// подключаем выполнение функций перемещения и отпускания клавиши мыши
// и завершаем выполнение функции, если нажата правая клавиша мыши
$(document).on('mousemove', mouseMove).on('mouseup', mouseUp).on('contextmenu', mouseUp);
});
});
Нерешённый вопрос: снимать выделение без использования спрятанного input (это простой путь, но совсем не феншуй). Кто что думает по поводу user-select?
Автор: rafaylik