📜 ⬆️ ⬇️

Sort using HTML5 Drag'n'Drop API

Sortable.js is a minimalist library for modern browsers and touch devices that does not require jQuery.

As you may have guessed from the name, the library is designed to sort items using drag'n'drop. The standard solution in such cases is jQuery UI / Sortable , and this is neither more nor less than 64 kb + 10 kb. Total 75 kb gzipped in a project where jQuery is not used at all. Relatively recently, Habré already had an article on how to implement similar functionality, but again on jQuery, and touch devices are not supported in the proposed solution.

In addition to problems with weight, all the libraries I found did not know how to work with a dynamically changeable list. At the time of the plug-in initialization, they determined the positions of all the elements, and in order to update them, it was necessary to reinitialize the plugin, or call the $('...').sortable('refresh') method, which is very inconvenient.
')
Since my task did not require the support of old browsers, I decided to try to make the functionality I needed on pure JS, using HTML5 Drag'n'Drop .

After reading the articles on this topic, it turned out that now it is very easy to create such functionality, you can even keep within 25 lines (if you remove the comments and jibe):

http://jsfiddle.net/RubaXa/zLq5J/
 function sortable(rootEl, onUpdate){ var dragEl; //     [].slice.call(rootEl.children).forEach(function (itemEl){ itemEl.draggable = true; }); //     function _onDragOver(evt){ evt.preventDefault(); evt.dataTransfer.dropEffect = 'move'; var target = evt.target; if( target && target !== dragEl && target.nodeName == 'LI' ){ //  rootEl.insertBefore(dragEl, target.nextSibling || target); } } //   function _onDragEnd(evt){ evt.preventDefault(); dragEl.classList.remove('ghost'); rootEl.removeEventListener('dragover', _onDragOver, false); rootEl.removeEventListener('dragend', _onDragEnd, false); //     onUpdate(dragEl); } //   rootEl.addEventListener('dragstart', function (evt){ dragEl = evt.target; //      //    evt.dataTransfer.effectAllowed = 'move'; evt.dataTransfer.setData('Text', dragEl.textContent); //     dnd rootEl.addEventListener('dragover', _onDragOver, false); rootEl.addEventListener('dragend', _onDragEnd, false); setTimeout(function (){ //      setTimeout,  //  ,    . dragEl.classList.add('ghost'); }, 0) }, false); } //  sortable( document.getElementById('list'), function (item){ console.log(item); }); 

As you can see from the code, the entire sorting consists of simply moving the element to be dragged with the help of rootEl.insertBefore(dragEl, target.nextSibling || target) , where target is the element rootEl.insertBefore(dragEl, target.nextSibling || target) at. If you have already tested an example, you probably noticed that you cannot drag an element to the first position. Another nuance of the method is that onUpdate is called every time, even if the element has not been moved.

In order to get rid of the first problem, it is enough to add a check when sorting. You need to insert an element after target.nextSibling only if it is not the first item in the list:

http://jsfiddle.net/RubaXa/zLq5J/3/
 if( target && target !== dragEl && target.nodeName == 'LI' ){ //  rootEl.insertBefore(dragEl, rootEl.children[0] !== target && target.nextSibling || target); } 

In addition, simply saving the link to the next element ( nextEl = dragEl.nextSibling ) at the moment of dragstart allows you to get rid of the second problem (http://jsfiddle.net/RubaXa/zLq5J/4/29 and 38 line).

At first glance, everything looks good, it turned out compact and clear code that most browsers support, and if you add support for attachEvent and remove dragEl.classList.add/remove , then the code will work even in IE5.5:]

But, if we change the example a little, simply by increasing the height of the list items, we get the third problem. Sorting normally works from top to bottom, but the opposite is already bad. Therefore, the logic for selecting the insertion of an element, “before” or “after,” needs to be rewritten to take into account in which half the mouse cursor is located, “upper” or “lower”. To do this, on onDragOver we get the coordinates of the element relative to the screen and check which half the cursor is in:

http://jsfiddle.net/RubaXa/zLq5J/6/
 var rect = target.getBoundingClientRect(); var next = (evt.clientY - rect.top)/(rect.bottom - rect.top) > .5; rootEl.insertBefore(dragEl, next && target.nextSibling || target); 

In addition, I still had to refine the work with inline-elements and float-blocks.


Touch support

Alas, drag'n'drop does not work on touch devices, so somehow it was necessary to do emulation based on touch events. I puzzled for a long time, read the documentation, but did not find the answer. As a result, having a little more rummaged, I remembered about the remarkable document.elementFromPoint method , which allows you to get a reference to an element by coordinates.

As a result, on the touchstart I clone an element that will act as a “ghost” under the finger and on the touchmove move it using translate3d :

 var touch = evt.touches[0] , dx = touch.clientX - tapEvt.clientX , dy = touch.clientY - tapEvt.clientY ; 

In addition, I run setInterval , in which every 100ms I check the element over which there is currently a finger:

 _emulateDragOver: function (){ if( touchEvt ){ //  “”   _css(ghostEl, 'display', 'none'); //  ,     var target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY); //        rootEl, //   onDragOver: this._onDragOver({ target: target , clientX: touchEvt.clientX , clientY: touchEvt.clientY }); //   “” _css(ghostEl, 'display', ''); } } 

That's all, nothing supernatural, as you can see, no. We make the code, we write a little documentation and the micro library is ready.


Sortable

The library turned out in the size of 2 kb gzipped and has the following features:



Code example:

 //  ,  ul > li var list = document.getElementById("my-ui-list"); new Sortable(list); //  . //  var foo = document.getElementById("foo"); new Sortable(foo, { group: "omega" }); var bar = document.getElementById("bar"); new Sortable(bar, { group: "omega" }); // handle + event var container = document.getElementById("multi"); new Sortable(container, { handle: ".tile__title", // css-,     dragabble: ".tile", // css- ,    onUpdate: function (evt/**Event*/){ var item = evt.detail; //   ,   } }); 


At the moment, there is only basic functionality, I will be glad to any feedback or pull request , thank you for your attention.


Demo | Source



You can also follow our projects through:
github.com/mailru - FileAPI, Tarantool, Fest and more
github.com/rubaxa - my github
@ibnRubaXa

Source: https://habr.com/ru/post/207048/


All Articles