preface
Drag and drop sorting should be familiar to your friends. In your daily work, you may choose to use open source libraries such as sortable.js to implement requirements. But after you’ve done your requirements, have you ever wondered how drag and drop sort works? I spent some time researching it and share it with you today.
implementation
HTML
<ul class="column">
<li class="column-item">item1</li>
<li class="column-item">item2</li>
<li class="column-item">item3</li>
<li class="column-item">item4</li>
<li class="column-item">item5</li>
<li class="column-item">item6</li>
<li class="column-item">item7</li>
<li class="column-item">item8</li>
<li class="column-item">item9</li>
<li class="column-item">item10</li>
</ul>
Copy the code
js
ES6 Class is used
class Draggable {
constructor(options) {
this.parent = options.element; // Parent element
this.cloneElementClassName = options.cloneElementClassName; // Clone the element class name
this.isPointerdown = false;
this.diff = { x: 0.y: 0 }; // The difference from the last move
this.drag = { element: null.index: 0.lastIndex: 0 }; // Drag and drop elements
this.drop = { element: null.index: 0.lastIndex: 0 }; // Release the element
this.clone = { element: null.x: 0.y: 0 };
this.lastPointermove = { x: 0.y: 0 };
this.rectList = []; // Used to save the data obtained by the drag item getBoundingClientRect()
this.init();
}
init() {
this.getRect();
this.bindEventListener();
}
// Get element position information
getRect() {
this.rectList.length = 0;
for (const item of this.parent.children) {
this.rectList.push(item.getBoundingClientRect()); }}handlePointerdown(e) {
// If the mouse clicks, only the left button responds
if (e.pointerType === 'mouse'&& e.button ! = =0) {
return;
}
if (e.target === this.parent) {
return;
}
this.isPointerdown = true;
this.parent.setPointerCapture(e.pointerId);
this.lastPointermove.x = e.clientX;
this.lastPointermove.y = e.clientY;
this.drag.element = e.target;
this.drag.element.classList.add('active');
this.clone.element = this.drag.element.cloneNode(true);
this.clone.element.className = this.cloneElementClassName;
this.clone.element.style.transition = 'none';
const i = [].indexOf.call(this.parent.children, this.drag.element);
this.clone.x = this.rectList[i].left;
this.clone.y = this.rectList[i].top;
this.drag.index = i;
this.drag.lastIndex = i;
this.clone.element.style.transform = 'translate3d(' + this.clone.x + 'px, ' + this.clone.y + 'px, 0)';
document.body.appendChild(this.clone.element);
}
handlePointermove(e) {
if (this.isPointerdown) {
this.diff.x = e.clientX - this.lastPointermove.x;
this.diff.y = e.clientY - this.lastPointermove.y;
this.lastPointermove.x = e.clientX;
this.lastPointermove.y = e.clientY;
this.clone.x += this.diff.x;
this.clone.y += this.diff.y;
this.clone.element.style.transform = 'translate3d(' + this.clone.x + 'px, ' + this.clone.y + 'px, 0)';
for (let i = 0; i < this.rectList.length; i++) {
// Collision detection
if (e.clientX > this.rectList[i].left && e.clientX < this.rectList[i].right &&
e.clientY > this.rectList[i].top && e.clientY < this.rectList[i].bottom) {
this.drop.element = this.parent.children[i];
this.drop.lastIndex = i;
if (this.drag.element ! = =this.drop.element) {
if (this.drag.index < i) {
this.parent.insertBefore(this.drag.element, this.drop.element.nextElementSibling);
this.drop.index = i - 1;
} else {
this.parent.insertBefore(this.drag.element, this.drop.element);
this.drop.index = i + 1;
}
this.drag.index = i;
const dragRect = this.rectList[this.drag.index];
const lastDragRect = this.rectList[this.drag.lastIndex];
const dropRect = this.rectList[this.drop.index];
const lastDropRect = this.rectList[this.drop.lastIndex];
this.drag.lastIndex = i;
this.drag.element.style.transition = 'none';
this.drop.element.style.transition = 'none';
this.drag.element.style.transform = 'translate3d(' + (lastDragRect.left - dragRect.left) + 'px, ' + (lastDragRect.top - dragRect.top) + 'px, 0)';
this.drop.element.style.transform = 'translate3d(' + (lastDropRect.left - dropRect.left) + 'px, ' + (lastDropRect.top - dropRect.top) + 'px, 0)';
this.drag.element.offsetLeft; // Trigger redraw
this.drag.element.style.transition = 'transform 150ms';
this.drop.element.style.transition = 'transform 150ms';
this.drag.element.style.transform = 'translate3d(0px, 0px, 0px)';
this.drop.element.style.transform = 'translate3d(0px, 0px, 0px)';
}
break; }}}}handlePointerup(e) {
if (this.isPointerdown) {
this.isPointerdown = false;
this.drag.element.classList.remove('active');
this.clone.element.remove(); }}handlePointercancel(e) {
if (this.isPointerdown) {
this.isPointerdown = false;
this.drag.element.classList.remove('active');
this.clone.element.remove(); }}bindEventListener() {
this.handlePointerdown = this.handlePointerdown.bind(this);
this.handlePointermove = this.handlePointermove.bind(this);
this.handlePointerup = this.handlePointerup.bind(this);
this.handlePointercancel = this.handlePointercancel.bind(this);
this.getRect = this.getRect.bind(this);
this.parent.addEventListener('pointerdown'.this.handlePointerdown);
this.parent.addEventListener('pointermove'.this.handlePointermove);
this.parent.addEventListener('pointerup'.this.handlePointerup);
this.parent.addEventListener('pointercancel'.this.handlePointercancel);
window.addEventListener('scroll'.this.getRect);
window.addEventListener('resize'.this.getRect);
window.addEventListener('orientationchange'.this.getRect);
}
unbindEventListener() {
this.parent.removeEventListener('pointerdown'.this.handlePointerdown);
this.parent.removeEventListener('pointermove'.this.handlePointermove);
this.parent.removeEventListener('pointerup'.this.handlePointerup);
this.parent.removeEventListener('pointercancel'.this.handlePointercancel);
window.removeEventListener('scroll'.this.getRect);
window.removeEventListener('resize'.this.getRect);
window.removeEventListener('orientationchange'.this.getRect); }}/ / instantiate
new Draggable({
element: document.querySelector('.column'),
cloneElementClassName: 'clone-column-item'
});
Copy the code
Demo: jsdemo. Codeman. Top/HTML/dragga…
Why not use the HTML drag and drop API implementation?
Since the native HTML drag and drop API is not available on the mobile side, the PointerEvent event is used to implement the drag logic for compatibility between PC and mobile.
Write in the last
So far, the basic function of drag and drop sort has been realized, but there are still many deficiencies. Features like nested drags, cross-list drags, and bottom-scrolling are not implemented. Interested partners can refer to the open source library for their own research.