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

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

概述

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

实现方法

新建拖拽实例

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>
相关推荐
小灰灰搞电子15 分钟前
Qt 队列容器 QQueue 的详解与示例
开发语言·qt·qqueue
码哥DFS30 分钟前
算法练习day1-备战2027届秋招
前端·javascript·数据结构·算法
张元清32 分钟前
React useInfiniteScroll Hook:无限滚动轻松实现(2026)
javascript·react.js
ACP广源盛1392462567340 分钟前
GSV6155@ACP# Type-C 视频信号转换芯片:技术架构、工程痛点适配与落地场景分析
大数据·c语言·开发语言·人工智能·分布式·嵌入式硬件·架构
别怪我很水41 分钟前
Vue element admin 浏览器本地存储 localStorage、useStorage
前端·javascript·vue.js
程序员-Benothing1 小时前
Java集合:List实现类深度对比
java·开发语言·后端·面试·list
酷在前行1 小时前
【R论文复刻】Nature Communications 冲积图进阶:状态转移、流带排序与多面板排版(保姆级教程)
开发语言·r语言
减瓦1 小时前
用 Git 为本地文件打造一台时光机
开发语言·git·编辑器
冻柠檬飞冰走茶1 小时前
PTA基础编程题目集 7-37 整数分解为若干项之和(C语言实现)
c语言·开发语言·数据结构·算法
晓得迷路了1 小时前
栗子前端技术周刊第 140 期 - pnpm、Node 新 API 文档网站、Oxlint...
前端·javascript·node.js