Создание простейшего компонента перетаскивания на Vue.js

Предположим, что у нас есть некоторый массив с элементами. Каждый из них представляет собой объект со свойством title. При перемещении элементов задействовано несколько событий: mousedown, mousemove и mouseover. Сначала необходимо разделить эти три события на отдельные этапы: кликаем по элементу (чтобы перетащить его), перетаскиваем и отпускаем.

Шаг 1

На первом этапе нужно определить, на каком элементе происходит перетаскивание, и сделать его слегка «прилипающим» к курсору. На данный момент у нас должно быть три метода:

methods: {
    myDrag(key, event) {
        this.draggableIndex = key;
        this.moveAt(event);
    },
    moveAt(event){
        if(this.draggableIndex !== -1){
            this.dragLeft = event.pageX - 12;
            this.dragTop = event.pageY - 12;
        }
    },
    myDragStop() {
        // сбрасываем все значения к начальным
        this.draggableIndex = -1;
        this.dragLeft = 0;
        this.dragTop = 0;
    },
    isDragging(key) {
        return this.draggableIndex === key;
    },
    positionByKey(key) {
        return this.isDragging(key) ? 'absolute' : 'relative'
    },
    leftByKey(key) {
        return this.isDragging(key) ? this.dragLeft + 'px' : 0
    },
    topByKey(key) {
        return this.isDragging(key) ? this.dragTop + 'px' : 0
    },
}

Число 12 – это координаты значка перетаскивания. Как же обработать draggableIndex, dragTop и dragLeft в шаблоне?

<template>
    <ul id="my-list" v-on:mousemove="moveAt">
        <li v-for="(item, key) in items" ref="li"
            v-bind:style="{
                position: positionByKey(key),
                left: leftByKey(key),
                top: topByKey(key),
            }">
            <i class="fa fa-arrows-alt"
               v-on:mousedown="myDrag(key, $event)"
               v-on:mouseup="myDragStop"></i>
            {{item.title}}
        </li>
    </ul>
</template>

Шаг 2

На данном этапе реализации выбранный элемент «прилипает» к курсору и следует за ним, пока мы не отпустим его. После «сброса» элемент должен вернуться в исходное положение, так как мы не рассчитали и не сохранили его новую позицию.

Шаг 2

Теперь необходимо сохранить обновлённые положения

Для этого создадим переменную reorderedList, в которой будем сохранять измененные данные до окончания перетаскивания. При его окончании мы присвоим reorderedList начальным элементам.

methods: {
    myDrag(key, event) {
        this.draggableIndex = key;
        this.reorderedList = this.items;
        this.moveAt(event);
    },
    moveAt(event){
        if(this.draggableIndex !== -1){
            let reorderedList=[];
            let lastI = -1;
            // получить отображаемые элементы списка 
            // и их координаты

            this.$refs.li.forEach((elem, i) => {
                // игнорируем перетаскиваемый элемент
                if (i !== this.draggableIndex) {
                    if (elem.offsetTop < event.pageY 
                        && i > this.draggableIndex) {
                        // если мы здесь, то элемент переместился вверх 
                        reorderedList[i - 1] = this.items[i];
                        if (lastI === -1 || lastI < i) {
                            lastI = i;
                        }
                    } else if (elem.offsetTop > event.pageY 
                        && i < this.draggableIndex) {
                        // если мы здесь, то элемент переместился вниз

                        reorderedList[i + 1] = this.items[i];
                        if (lastI === -1 || lastI > i) {
                            lastI = i;
                        }
                    } else {
                        //иначе его позиции не изменились
                        reorderedList[i] = this.items[i];
                    }
                }
            });
            // если позиции изменились, мы должны переопределить элементы
            if (lastI !== -1) {
                reorderedList[lastI] = this.items[this.draggableIndex];
                this.reorderedList = Object.assign([], reorderedList, reorderedList);
            }
            this.dragLeft = event.pageX - 16;
            this.dragTop = event.pageY - 16;
        }
    },
    myDragStop() {
        this.draggableIndex = -1;
        this.dragLeft = 0;
        this.dragTop = 0;
        
        this.items = Object.assign([], this.reorderedList, this.reorderedList);
    }
    ...
}

Заключение

Мы получили самый простой способ реализации перетаскивания элемента!

Наталья Кайдаавтор-переводчик статьи «The Simplest Drag and Drop Component on Vue.js»