js to achieve drag and drop sorting details

js to achieve drag and drop sorting details

1. Introduction

Drag-and-drop sorting should be familiar to everyone. When working normally, you may choose to use open source libraries such as Sortable.js to meet your needs. But after meeting the requirements, have you ever thought about how to achieve drag and drop sorting? I spent some time researching it and I’m sharing it with you today.

2. Implementation

 {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

.grid {
    display: flex;
    flex-wrap: wrap;
    margin: 0 -15px -15px 0;
    touch-action: none;
    user-select: none;
}

.grid-item {
    width: 90px;
    height: 90px;
    line-height: 88px;
    text-align: center;
    margin: 0 15px 15px 0;
    background: #FFF;
    border: 1px solid #d6d6d6;
    list-style: none;
}

.active {
    background: #c8ebfb;
}

.clone-grid-item {
    position: fixed;
    left: 0;
    top: 0;
    z-index: 1;
    width: 90px;
    height: 90px;
    line-height: 88px;
    text-align: center;
    background: #FFF;
    border: 1px solid #d6d6d6;
    opacity: 0.8;
    list-style: none;
}

<ul class="grid">
    <li class="grid-item">item1</li>
    <li class="grid-item">item2</li>
    <li class="grid-item">item3</li>
    <li class="grid-item">item4</li>
    <li class="grid-item">item5</li>
    <li class="grid-item">item6</li>
    <li class="grid-item">item7</li>
    <li class="grid-item">item8</li>
    <li class="grid-item">item9</li>
    <li class="grid-item">item10</li>
</ul>

Using ES6 Class writing:

class Draggable {
    constructor(options) {
        this.parent = options.element; // Parent element this.cloneElementClassName = options.cloneElementClassName; // Clone element class name this.isPointerdown = false;
        this.diff = { x: 0, y: 0 }; // Difference relative to the last move this.drag = { element: null, index: 0, lastIndex: 0 }; // Drag element this.drop = { element: null, index: 0, lastIndex: 0 }; // Release 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() method 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 it is a mouse click, only respond to the left button 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('.grid'),
    cloneElementClassName: 'clone-grid-item'
});

Demo: jsdemo.codeman.top/html/dragga…

3. Why not use HTML drag and drop API?

Because the native HTML drag and drop API cannot be used on mobile devices, PointerEvent event is used to implement the drag logic in order to be compatible with both PC and mobile devices.

4. Summary

The basic function of drag-and-drop sorting has been implemented, but there are still many shortcomings. Features like nested dragging, dragging across lists, and automatic scrolling when dragging to the bottom are not implemented.

This is the end of this article about the details of implementing drag and drop sorting with js. For more relevant content about implementing drag and drop sorting with js, please search for previous articles on 123WORDPRESS.COM or continue to browse the related articles below. I hope you will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • Antdesign-vue combined with sortablejs to achieve the function of dragging and sorting two tables
  • Source code example of image drag and drop sorting based on js
  • React.js component implements drag and drop sorting component function process analysis
  • Elementui table component + sortablejs to implement row drag and drop sorting sample code
  • Implementing drag-and-drop sorting based on AngularJS drag-and-drop plug-in ngDraggable.js
  • JS drag and drop sorting plug-in Sortable.js usage example analysis
  • React.js component implements drag-and-drop copy and sortable sample code
  • JS implements simple image drag and drop sorting example code

<<:  25 Examples of Using Circular Elements in Web Design

>>:  Docker uses Git to implement the detailed process of Jenkins release and test projects

Recommend

Share the responsive frameworks commonly used by web design masters (summary)

This article introduces and shares the responsive...

Detailed tutorial on how to quickly install Zookeeper in Docker

Docker Quickly Install Zookeeper I haven't us...

Detailed explanation of Mysql's concurrent parameter adjustment

Table of contents Query cache optimization Overvi...

Quickly solve the problem of slow startup after Tomcat reconfiguration

During the configuration of Jenkins+Tomcat server...

MySql common query command operation list

MYSQL commonly used query commands: mysql> sel...

Execute the shell or program inside the Docker container on the host

In order to avoid repeatedly entering the Docker ...

MySQL service and database management

Table of contents 1. Start and stop service instr...

How to implement function currying and decurrying in Javascript

Function currying (black question mark face)? ? ?...

Installation and use tutorial of Elasticsearch tool cerebro

Cerebro is an evolution of the Elasticsearch Kopf...

How to use Vue+ElementUI Tree

The use of Vue+ElementUI Tree is for your referen...

MySQL 8.0.18 uses clone plugin to rebuild MGR implementation

Assume that a node in the three-node MGR is abnor...

Summary of several error logs about MySQL MHA setup and switching

1: masterha_check_repl replica set error replicat...

React.js framework Redux basic case detailed explanation

react.js framework Redux https://github.com/react...