API перетаскивания HTML5 реализует компоненты перетаскивания дерева vue.

Node.js внешний интерфейс Vue.js Firefox

Поскольку для бизнес-сценария требуется древовидный компонент, который можно перетаскивать для изменения положения узла, я взял один из них и воспользовался возможностью, чтобы прикоснуться к собственному перетаскиванию html5. В ближайшее время у меня будет время извлечь основную часть кода и вкратце описать метод реализации.

1. Древовидная структура - компоненты используются рекурсивно

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

tree.vue


<template>
  <div>
    <Tree-Node v-for="item in data" :key="item.title" :node-data="item"></Tree-Node>
  </div>
</template>

Компоненты Vue позволяют вызывать себя в собственных шаблонах, поэтому они могут образовывать древовидную структуру, а в компоненте должно быть указано уникальное имя.

tree-node.vue

<template>
  <transition name="slide-up">
    <ul :class="classes">
      <li>
        <div :class="[prefixCls + '-item']">
          <i class="sp-icon sp-icon-arrow-right" :class="arrowClasses" @click.stop="toggleCollapseStatus()"></i>
          <span :class="[prefixCls + '-title-wrap']" ref="dropTarget">
            <span :class="[dragClasses,dragOverClass]" ref="draggAbleDom" v-html="nodeData.title"></span>
          </span>
        </div>
        <Tree-Node v-for="item in nodeData.children" :key="item.title" :node-data="item" v-show="nodeData.children.length && nodeData.isExpand"></Tree-Node>
      </li>
    </ul>
  </transition>
</template>

2.HTML5 API перетаскивания

1. Атрибут draggable указывает, можно ли перетаскивать элемент.В настоящее время Internet Explorer 9+, Firefox, Opera, Chrome и Safari поддерживают атрибут draggable. 2. API перетаскивания HTML 5

  • ondragstart: срабатывает, когда элемент начинает перетаскиваться на перетаскиваемый элемент
  • ondragenter: событие срабатывает, когда перетаскиваемый элемент входит в целевой элемент, воздействуя на целевой элемент.
  • ondragover: событие срабатывает, когда перетаскиваемый элемент перемещается по целевому элементу, воздействуя на целевой элемент.
  • ondragleave: срабатывает, когда перетаскиваемый элемент перетаскивается от целевого элемента, воздействуя на целевой элемент
  • ondrop: перетаскивая элемент на целевой элемент, в то же время отпускайте событие, вызванное мышью, воздействуйте на целевой элемент
  • ondragend: Когда событие запускается после завершения перетаскивания, действующего на буксируемый элемент

3. Перетащите узлы

определить переменную

Для обработки узлов перетаскивания требуется несколько ключевых переменных.

  • текущий перетаскиваемый узел
  • Узлы, через которые нужно пройти при перетаскивании
  • последний размещенный узел

Итак, определите объект для сохранения информации о перетаскивании.

dragOverStatus: {
    overNodeKey: "",
    dropPosition: "",
    dragNode: {}
}

Привязать событие перетаскивания

Здесь событие ondragstart привязано к дочернему элементу, а другие события привязаны к родительскому элементу, потому что при тестировании реальной машины IE10 обнаружено, что ondragstart и другие события привязаны к одному и тому же элементу, а такие события, как ondragenter не может быть запущен.

<span :class="[prefixCls + '-title-wrap']" ref="dropTarget">
    <span :class="[dragClasses,dragOverClass]" ref="draggAbleDom" v-html="nodeData.title"></span>
</span>

 mounted() {
    //绑定拖拽事件
    if (this.root.draggable) {
      this.$refs.draggAbleDom.draggable = !this.nodeData.noDrag;
      this.$refs.draggAbleDom.ondragstart = this.onDragStart;

      this.$refs.dropTarget.ondragenter = this.onDragEnter;
      this.$refs.dropTarget.ondragover = this.onDragOver;
      this.$refs.dropTarget.ondragleave = this.onDragLeave;
      this.$refs.dropTarget.ondrop = this.onDrop;
      this.$refs.dropTarget.ondragend = this.onDragEnd;
    }
  }

Когда событие перетаскивания узла запускается, текущий экземпляр узла может быть получен из события перетаскивания. Используя специальный API перетаскивания, предоставляемый HTML5, сложные операции реализуются изначально, и нет необходимости самостоятельно имитировать события мыши, поэтому реализовать эффект перетаскивания очень просто.

(1) Начать перетаскивание: Запускается на перетаскиваемом элементе, в событии необходимо сохранить только информацию о текущем перетаскиваемом узле.

onDragStart(e, treeNode) {
      this.dragOverStatus.dragNode = {
        nodeData: treeNode.nodeData,
        parentNode: treeNode.parentNodeData
      };
      this.$emit("on-dragStart", {
        treeNode: treeNode.nodeData,
        parentNode: treeNode.parentNodeData,
        event: e
      });
    }

(2) Войдите в целевой узел: активируйте целевой элемент, в основном сохраните ключ узла, через который в настоящее время проходит, а затем отправьте событие на внешний уровень, чтобы вызывающая сторона компонента выполнила другие операции. Чтобы избежать частых событий при быстром перетаскивании элемента через множество узлов, установите таймер на срабатывание после пребывания в течение определенного периода времени.

onDragEnter(e, treeNode) {
      //当没有设置拖拽节点时,禁止作为目标节点
      if (!this.hasDragNode()) {
        return;
      }
      this.dragOverStatus.overNodeKey = "";
      //拖拽节点与目标节点是同一个,return掉
      if (
        treeNode.nodeData._hash === this.dragOverStatus.dragNode.nodeData._hash
      ) {
        return;
      }
      this.dragOverStatus.overNodeKey = treeNode.nodeData._hash; //当前经过的可放置的节点的key
      //当前节点禁止做为放置节点时
      if (treeNode.nodeData.noDrop) {
        return;
      }
      //设置dragEnter定时器,停留250毫秒后触发事件
      if (!this.delayedDragEnterLogic) {
        this.delayedDragEnterLogic = {};
      }
      Object.keys(this.delayedDragEnterLogic).forEach(key => {
        clearTimeout(this.delayedDragEnterLogic[key]);
      });
      this.delayedDragEnterLogic[
        treeNode.nodeData._hash
      ] = setTimeout(() => {
        if (!treeNode.nodeData.isExpand) {
          treeNode.toggleCollapseStatus();
        }
        this.$emit("on-dragEnter", {
          treeNode: treeNode.nodeData,
          parentNode: treeNode.parentNodeData,
          event: e
        });
      }, 250);
    }

(3) Передать целевой узел: запустить целевой элемент, вычислить положение мыши на целевом узле в режиме реального времени и использовать его для определения конечной позиции размещения, 0 (как дочерний узел целевого узла ), -1 (размещается на целевом узле) перед узлом), 1 (размещается после целевого узла), отображают соответствующий стиль.

onDragOver(e, treeNode) {
      //当没有设置拖拽节点时,禁止作为目标节点
      if (!this.hasDragNode()) {
        return;
      }
      if (
        this.dragOverStatus.overNodeKey === treeNode.nodeData._hash
      ) {
        this.dragOverStatus.dropPosition = this.calDropPosition(e); //放置标识0,-1,1
      }
      this.$emit("on-dragOver", {
        treeNode: treeNode.nodeData,
        parentNode: treeNode.parentNodeData,
        event: e
      });
      this.dragOverClass = this.setDragOverClass();//设置鼠标经过样式
    },

Когда мышь находится над целевым узлом (1/5) в целевом узле, это означает, что она должна быть размещена перед целевым узлом - на том же уровне, когда мышь находится ниже целевого узла (1/5) в целевом узле. целевой узел, значит быть размещенным в целевом узле.После целевого узла - sibling, иначе как дочерний узел целевого узла

calDropPosition(e) {
      var offsetTop = this.getOffset(e.target).top;
      var offsetHeight = e.target.offsetHeight;
      var pageY = e.pageY;
      var gapHeight = 0.2 * offsetHeight;
      if (pageY > offsetTop + offsetHeight - gapHeight) {
        //放在目标节点后面-同级
        return 1;
      }
      if (pageY < offsetTop + gapHeight) {
        //放在目标节点前面-同级
        return -1;
      }
      //放在目标节点里面-作为子节点
      return 0;
    }

(4).Place node: Запускается на целевом элементе.В это время перетаскиваемая информационная переменная используется в качестве параметра для отправки события на внешний слой, а остальные операции могут определяться внешним слоем.

onDrop(e, treeNode) {
      //当没有设置拖拽节点时,禁止作为目标节点
      if (!this.hasDragNode()) {
        return;
      }
      //当前节点禁止拖拽时
      if (treeNode.nodeData.noDrop) {
        return;
      }
      //拖拽节点与目标节点是同一个,不做任何操作
      if (
        this.dragOverStatus.dragNode.nodeData._hash === treeNode.nodeData._hash
      ) {
        return;
      }

      var res = {
        event: e,
        dragNode: this.dragOverStatus.dragNode,
        dropNode: {
          nodeData: treeNode.nodeData,
          parentNode: treeNode.parentNodeData
        },
        dropPosition: this.dragOverStatus.dropPosition
      };
      this.$emit("on-drop", res);
    }

(5).Конец перетаскивания: Воздействует на перетаскиваемый элемент.После завершения перетаскивания переменная очищается и стиль восстанавливается.

onDragEnd(e, treeNode) {
      //当没有设置拖拽节点时,禁止作为目标节点
      if (!this.hasDragNode()) {
        return;
      }
      //当前节点禁止拖拽时
      if (treeNode.nodeData.noDrop) {
        return true;
      }
      this.dragOverStatus.dragNode = null;
      this.dragOverStatus.overNodeKey = "";

      this.$emit("on-dragEnd", {
        treeNode: treeNode.nodeData,
        parentNode: treeNode.parentNodeData,
        event: e
      });
    }

4. Применение

Вызовите древовидный компонент перетаскивания, чтобы получить узел перетаскивания, целевой узел и положение размещения во время процесса перетаскивания.Конкретная обработка результата перетаскивания определяется вызывающей стороной, которая может заключаться в обновлении древовидной структуры путем настройки интерфейса, или для обработки входных данных во внешнем интерфейсе. , для обновления представления.

<template>
    <Tree :data="data1" draggable @on-drop="getDropData">
    </Tree>
</template>
getDropData(info) {
      var dragData = info.dragNode.nodeData;
      var dragParent = info.dragNode.parentNode;
      var dropData = info.dropNode.nodeData;
      var dropParent = info.dropNode.parentNode;
      var dropPosition = info.dropPosition; //0作为子级,-1放在目标节点前面,1放在目标节点后面

      //把拖拽元素从父节点中删除
      dragParent.children.splice(dragParent.children.indexOf(dragData), 1);
      if (dropPosition === 0) {
        dropData.children.push(dragData);
      } else {
        var index = dropParent.children.indexOf(dropData);
        if (dropPosition === -1) {
          dropParent.children.splice(index, 0, dragData);
        } else {
          dropParent.children.splice(index + 1, 0, dragData);
        }
      }
    }

В качестве дочернего узла измените иерархию

Измените порядок и поместите перетаскиваемый узел за целевой узел.

Изменить упорядочение и поместить узел перетаскивания перед целевым узлом

Исходный код здесь