将容器内的元素变为可拖拽

将容器内的元素变为可拖拽

概述

在容器内,我们经常需要将里面的元素变为可拖拽的。例如,你可能想要让用户能够通过拖动来重新排列列表中的项目或者移动图片等。

实现方法

新建拖拽实例

javascript 复制代码
// drag.js
// 将容器内的元素变为可拖拽
const DRAG_ITEM_MASK = "drag__item__mask",
  MOVING = "moving";
export class Drag {
  constructor(container) {
    this.sourceNode = null;
    this.dragItemIndex = null; // 拖拽时dom的索引,拖拽开始时确定值
    this.sourceIndex = null; // 拖拽过程中当前dom的索引,会随着位置改变而改变
    this.targetIndex = null; // 鼠标进入的目标dom的索引,会随着位置改变而改变
    this.container = container;
    this._init();
  }

  _init() {
    const _this = this;
    let children = Array.from(this.container.children);
    _this._setDraggable(children, true);
    this.container.ondragstart = this._ondragstart.bind(this);
    this.container.ondragenter = this._ondragenter.bind(this);
    this.container.ondragend = this._ondragend.bind(this);
  }
  // 拖动开始
  _ondragstart(e) {
    if (!e.target.draggable) return;
    let children = Array.from(this.container.children);
    this._createMask(children);
    setTimeout(() => {
      e.target.classList.add(MOVING);
      const mask = e.target.querySelector(`.${DRAG_ITEM_MASK}`);
      mask.style.background = "#fff";
      mask.style.border = "2px dashed #ff0000";
    }, 0);
    this.sourceNode = e.target;
    this.dragItemIndex = children.indexOf(e.target);
  }
  // 进入某一个可拖动项
  _ondragenter(e) {
    if (!this.sourceNode || e.target.className !== DRAG_ITEM_MASK) {
      return;
    }
    const container = this.container;
    let targetNode = e.target.parentElement;
    let sourceNode = this.sourceNode;
    if (targetNode === container || targetNode === sourceNode) {
      return;
    }
    let children = Array.from(container.children);
    this.sourceIndex = children.indexOf(sourceNode);
    this.targetIndex = children.indexOf(targetNode);

    if (this.targetIndex < 0) return;
    if (this.sourceIndex < this.targetIndex) {
      // 向下拖动
      container.insertBefore(sourceNode, targetNode.nextElementSibling);
    } else {
      // 向上拖动
      container.insertBefore(sourceNode, targetNode);
    }
  }
  // 拖动结束
  _ondragend(e) {
    if (!e.target.draggable) return;
    e.target.classList.remove(MOVING);
    let children = Array.from(this.container.children);
    this._removeMask(children);
    const targetIndex = children.indexOf(e.target);
    this.sourceNode = null;
    this.ondragend &&
      this.ondragend({
        sourceIndex: this.dragItemIndex,
        targetIndex: targetIndex,
      });
  }

  _setDraggable(elements, bool = true) {
    elements.forEach((element) => {
      if (!element.dataset.nodrag) {
        element.draggable = bool;
        element.style.cursor = bool ? "move" : "default";
      }
    });
  }

  _createMask(elements = []) {
    elements.forEach((el) => {
      if (!el.dataset.nodrag) {
        const mask = document.createElement("div");
        mask.className = DRAG_ITEM_MASK;
        mask.style.position = "absolute";
        mask.style.inset = 0;
        mask.style.zIndex = 9999;
        el.style.position = "relative";
        el.appendChild(mask);
      }
    });
  }

  _removeMask(elements = []) {
    elements.forEach((el) => {
      const mask = el.querySelector(`.${DRAG_ITEM_MASK}`);
      mask?.remove();
    });
  }

  distroy() {
    if (this.container) {
      let children = Array.from(this.container.children);
      this._setDraggable(children, null);
      this.container.ondragstart = null;
      this.container.ondragenter = null;
      this.container.ondragend = null;
      this.ondragend = null;
    }
  }
}

使用示例

在指令中使用

js 复制代码
const drag = {
  mounted(el, { value }, vnode) {
    // 指令绑定到元素时调用
    if (!!value === true) {
      const onDragChange = vnode.props["on:dragChange"] ?? (() => {});
      el.drag = new Drag(el);
      el.drag.ondragend = onDragChange;
    }
  },
  updated(el, { value }, vnode) {
    // 元素更新前调用
    if (!!value === false && el.drag) {
      el.drag?.distroy();
      el.drag = null;
    } else if (!!value === true) {
      const onDragChange = vnode.props["on:dragChange"] ?? (() => {});
      el.drag = new Drag(el);
      el.drag.ondragend = onDragChange;
    }
  },
  beforeUnmount(el) {
    // 元素解绑时调用
    el.drag?.distroy();
    el.drag = null;
  },
};

在组件中使用指令 v-drag

vue 复制代码
<div class="drag-box" v-drag="true"></div>
相关推荐
JieE2121 天前
LeetCode 101. 对称二叉树|JS 递归 + 迭代双解法,彻底搞懂镜像判断
javascript·算法
冬奇Lab1 天前
AI Workflow 定义的四次演进:从 Markdown 到 JS 脚本,再到分布式多 Agent
javascript·人工智能·agent
一颗烂土豆1 天前
Meshopt 压缩深度解析,为什么它比 Draco 更快
前端·javascript·webgl
kyriewen1 天前
同事每天催我 Code Review,我写了个脚本让 AI 替我 review PR——现在他反过来催 AI 了
前端·javascript·ai编程
weedsfly2 天前
迭代器、生成器与异步迭代——让数据“按需流动”的艺术
前端·javascript
假如让我当三天老蒯2 天前
前端跨域解决方案(学习用)
前端·javascript·面试
铁皮饭盒2 天前
Bun 哪比 Node.js 快?
javascript·后端
JieE2122 天前
LeetCode 56. 合并区间|超清晰 JS 图解思路,面试高频区间题
javascript·算法·面试
candyTong2 天前
RTK 技术原理:一次典型会话里,80% 上下文是怎么省下来的
javascript·后端·架构
_柳青杨2 天前
深入理解 JavaScript 事件循环
前端·javascript