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

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

概述

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

实现方法

新建拖拽实例

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>
相关推荐
开开心心就好几秒前
以图搜图找重复图片,本地工具离线就能用
javascript·智能手机·ffmpeg·c#·ocr·word·音视频
夏天的清晨4 分钟前
C++入门学习
开发语言·c++·学习
新鲜势力呀7 分钟前
PHP 文件上传系统实战:从上传卡顿到分片上传 + 秒传 + 云存储架构完整优化方案
开发语言·架构·php
企业数字化笔记9 分钟前
视频局部合成怎么做得自然?掩膜修复、边缘羽化、时序稳定与背景替换
java·javascript·音视频
程序员老陆23 分钟前
从语法糖到类型系统:深度拆解 C++ Lambda 与可调用对象的“血缘关系”
开发语言·c++·程序设计
(Charon)24 分钟前
【C++面试】设计模式:手写单例模式与工厂模式
开发语言·c++
估值探索者25 分钟前
【Python量化系统工程实战 #01】数据存储选型 CSVSQLiteMySQL 对比与 SQLite 实战建库
开发语言·jvm·python·sqlite·api接口·数据api接口·股票数据api接口
根目录下的猫32 分钟前
ROS 2 C++(rclcpp)版本,实现话题订阅、话题发布,以及完整编译运行流程
开发语言·c++·算法
сокол1 小时前
【Python-基础-环境搭建】Ubuntu Python 开发环境搭建:编译、pipenv、pyenv、远程连接
开发语言·python·ubuntu
liangshanbo12153 小时前
JavaScript 面试题:Object.keys() 的返回顺序是无序的吗?
开发语言·javascript·ecmascript