Vue 实现拖拽模块(二)自定义拖拽组件位置

news2024/12/23 2:55:38

上文介绍了 拖拽添加组件 的简单实现,本文将继续给大家分享如何自定义拖拽组件位置的简单实现,文中通过示例代码介绍,感兴趣的小伙伴们可以了解一下

本文主要介绍了 Vue自定义拖拽组件位置的简单实现,具体如下:

效果图
拖拽图片

实现过程

  1. 给画布中的拖拽元素绑定 onmousedown 事件并把组件身上绑定的属性和在拖拽元素列表的位置传到事件处理函数中
  2. 在 data 中声明一个 标识位(下面会说到) 和 保存鼠标点击位置的对象(containerMoveObj )
  3. 在拖拽元素的 mousedown 事件中,从事件对象上面获取到鼠标当前点击的位置 event.pageX 和 event.pageY, 并记录到 containerMoveObj 的变量中,最后把标识位设置为 move (移动)
  4. 给画布绑定 mousemove 事件,首先判断标识位是否为 move ,在从事件对象上面获取到鼠标移动的位置 event.pageX 和 event.pageY 同时减去对应点击位置的 pageX 和 pageY,就得到了鼠标移动的距离,再加上拖拽元素本身的位置就得到了,元素移动的实际位置并赋值到拖拽元素本身上面。
  5. 给画布绑定 mouseup 事件,并获取拖拽元素身上的 position的 x 和 y 值,循环拖拽元素的集合匹配唯一标识 identifier 找到当前移动的元素,给这个元素赋值 temp.position 和 position 的 x 和 y 值,并把标识位重置为空,阻止画布持续触发 mousemove 和 mouseup 事件

完整代码

<template>
  <div class="box">
    <!-- 左侧拖拽组件 -->
    <!-- v-if="false" -->
    <div class="drap">
      <!-- <p>元素</p> -->
      <!-- 
            @dragstart  < -- 是元素开始拖拽的时候触发
            draggable="true"  < -- 为了使元素可拖动,把 draggable 属性设置为 true :
            @dragover.prevent < -- 阻止浏览器默认行为,不然会显示一个叉叉,不好看, 加上会显示一个添加的符号
         -->
      <div
        v-for="(item, index) in drapLeftElList"
        class="drap-item"
        :key="index"
        @dragstart="handleDrapEvList($event, item)"
        @dragover.prevent
        draggable="true"
      >
        <img
          class="drap-item-img"
          draggable="false"
          :src="item.imgUrl"
          :alt="item.name"
        />
        <div class="drap-item-name">{{ item.name }}</div>
      </div>
    </div>
    <!-- 主体部分 -->
    <div
      class="drap-container"
      @dragover.prevent
      @mousedown="laryerMouseDown"
      @mousemove="laryerMouseMove"
      @mouseup="laryerMouseUp"
      @drop="handleDrap"
    >
      <h1>画布</h1>
      <div
        v-for="(item, index) in componentsList"
        class="drap-container-item"
        :class="{
          'drap-container-item-active':
            curControl && item.identifier == curControl.identifier,
        }"
        :key="index"
        :style="{
          top: `${item.position.y}px`,
          left: `${item.position.x}px`,
          width: `${item.position.w}px`,
          height: `${item.position.h}px`,
          'background-color': `${item.position.bg}`,
        }"
        @mousedown.stop="handleMouseDown($event, item, index)"
      >
        <img
          class="drap-item-img"
          :src="item.imgUrl"
          draggable="false"
          :alt="item.name"
        />
        <div class="drap-item-name">{{ item.name }}</div>
      </div>
    </div>
    <!-- 属性配置 -->
    <div class="drap-right" style="width: 300px; height: 100%">
      <h2>属性配置</h2>
      {{ identifier }}
      <br />
      {{ curControl }}
      <br />
      {{ containerMoveObj }}
    </div>
  </div>
</template>

<script>
export default {
  name: "drap",
  data() {
    return {
      // 保存拖拽的元素的列表
      componentsList: [
        {
          id: 11,
          name: "团队1",
          imgUrl:
            "http://192.168.0.103/admin/sys-file/ydcloud/4e4ab580ac7549bfb0b7b46db57b3bb4.png",
          sort: 1,
          identifier: 666,
          position: {
            x: 100,
            y: 100,
            w: 80,
            h: 120,
            bg: "#ffffff",
          },
          style: {},
          temp: {
            position: {
              x: 100,
              y: 100,
            },
          },
        },
      ],
      //   元件库
      drapLeftElList: [
        {
          id: 11,
          name: "团队1",
          imgUrl:
            "http://192.168.0.103/admin/sys-file/ydcloud/4e4ab580ac7549bfb0b7b46db57b3bb4.png",
          sort: 1,
          position: {
            x: 0,
            y: 0,
            w: 80,
            h: 120,
            bg: "#fff",
          },
          temp: {
            position: {
              x: 0,
              y: 0,
            },
          },
        },
        {
          id: 13,
          name: "团队2",
          imgUrl:
            "http://192.168.0.103/admin/sys-file/ydcloud/4e4ab580ac7549bfb0b7b46db57b3bb4.png",
          sort: 2,
          position: {
            x: 0,
            y: 0,
            w: 80,
            h: 120,
            bg: "#fff",
          },
          temp: {
            position: {
              x: 0,
              y: 0,
            },
          },
        },
        {
          id: 14,
          name: "团队3",
          imgUrl:
            "http://192.168.0.103/admin/sys-file/ydcloud/4e4ab580ac7549bfb0b7b46db57b3bb4.png",
          sort: 3,
          position: {
            x: 0,
            y: 0,
            w: 80,
            h: 120,
            bg: "#fff",
          },
          temp: {
            position: {
              x: 0,
              y: 0,
            },
          },
        },
        {
          id: 15,
          name: "团队4",
          imgUrl:
            "http://192.168.0.103/admin/sys-file/ydcloud/4e4ab580ac7549bfb0b7b46db57b3bb4.png",
          sort: 3,
          position: {
            x: 0,
            y: 0,
            w: 80,
            h: 120,
            bg: "#fff",
          },
          temp: {
            position: {
              x: 0,
              y: 0,
            },
          },
        },
      ],
      identifier: "", // 当前项的 唯一标识
      curControl: null, //
      flag: "",
      containerMoveObj: {
        type: "",
        x: "",
        y: "",
      },
    };
  },
  methods: {
    // 点击画布的时候, 取消选择组件
    laryerMouseDown() {
      console.log("laryerMouseDown");
      this.curControl = null;
    },
    // 给画布绑定的mousemove事件
    laryerMouseMove(ev) {
      // 判断是需要移动的类型
      if (this.flag == "move") {
        // 用当前移动的距离减去点击的位置
        let dx = ev.pageX - this.containerMoveObj.x,
          dy = ev.pageY - this.containerMoveObj.y;

        // 上次旧的位置加上 处理完的距离就得到当前位置
        let x = this.curControl.temp.position.x + dx,
          y = this.curControl.temp.position.y + dy;
        // 这里只是让元素跟着鼠标移动, 如果再这里直接赋值
        this.curControl.position.x = x;
        this.curControl.position.y = y;
      }
    },
    // 给画布绑定的mouseup事件
    laryerMouseUp() {
      // 在鼠标抬起的时候判断是否
      if (this.flag == "") {
        return false;
      }
      const x = this.curControl.position.x;
      const y = this.curControl.position.y;
      // 这里才是实际给元素位置赋值的地方!!!!
      // 查询是否有对应的模块然后, 对应的赋值
      this.componentsList.forEach((item) => {
        if (item.identifier == this.identifier) {
          console.log(item, "找到了");

          item.temp.position.x = x;
          item.temp.position.y = y;

          item.position.x = x;
          item.position.y = y;
        }
      });

      this.flag = "";
    },

    // 拖拽元素
    handleDrapEvList(event, value) {
      let { offsetX, offsetY } = event;
      var infoJson = JSON.stringify({
        ...value,
        position: {
          ...value.position,
          x: offsetX,
          y: offsetY,
        },
      });
      //   将数据绑定到dataTransfer身上
      event.dataTransfer.setData("drapData", infoJson);
    },
    // 监听拖拽元素结束
    handleDrap(event) {
      event.preventDefault();
      const value = event.dataTransfer.getData("drapData");
      //   获取绑定到拖拽元素身上的 drapData属性
      if (value) {
        let drapData = JSON.parse(value);
        const { position } = drapData;
        const identifier = Math.floor(Math.random() * 10000);
        this.componentsList.push({
          ...drapData,
          identifier,
          position: {
            ...position,
            x: event.offsetX - position.x,
            y: event.offsetY - position.y,
          },
          temp: {
            position: {
              x: event.offsetX - position.x,
              y: event.offsetY - position.y,
            },
          },
        });
      }
    },
    // 点击元素获取组件配置
    handleClickTarget(row, index) {
      console.log(row);
      this.identifier = row.identifier;
      this.curControl = row;
    },

    // 移动元素
    handleMouseDown(e, row, index) {
      this.flag = "move";
      // 获取组件配置, 为接下来的属性配置做准备
      this.handleClickTarget(row, index);
      e = e || window.event;

      // 记录下当前点击的位置
      this.containerMoveObj.x = e.pageX;
      this.containerMoveObj.y = e.pageY;
    },
  },
};
</script>

<style lang="scss">
.box {
  display: flex;
  flex-direction: row;
  align-items: center;
  position: relative;
  height: 500px;
  .drap {
    width: 300px;
    height: 500px;
    background: #f2f2f2;
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    cursor: pointer;
    .drap-item {
      height: 120px;
      margin-right: 20px;
      .drap-item-img {
        display: block;
        width: 80px;
        height: 80px;
      }
      .drap-item-name {
        text-align: center;
      }
    }
  }
  .drap-container {
    flex: 1;
    height: 500px;
    background: #ccc;
    position: relative;

    .drap-container-item {
      -webkit-user-select: none;
      -moz-user-select: none;
      -o-user-select: none;
      user-select: none;
      position: absolute;
      user-select: none;
      cursor: pointer;
      border: 1px solid transparent;
      .drap-item-img {
        display: block;
        width: 100%;
        // height: 80px;
        user-select: none;
      }
      .drap-item-name {
        text-align: center;
      }
    }
    .drap-container-item-active {
      border: 1px solid skyblue;
    }
  }
}
</style>

问题答疑

Q:为什么要分开绑定 mousedown 和 mousemove、mouseup ?
A:在给画布绑定了 drop 事件之后,像传统移动一样给拖拽元素绑定这几个事件的话,会导致元素回到起始位置(左上角),会出现元素不跟鼠标等问题

Q:标识位作用是什么 ?
A:因为 mousemove 和 mouseup 是绑定到画布上面的,鼠标在画布上移动的时候会持续触发这两个事件,所以要用标识位来阻止无用的触发。

Q:为什么要用 pageX 和 pageY 而不用 offset 去获取移动距离 ?
A:因为 mousedown 和 mousemove、mouseup是分开绑定的,所以继续使用 offset 来计算移动位置就会变得不精确而且麻烦,这种情况下需要我们用其他办法去处理并计算移动位置。需要我们

获取鼠标点下时距离页面左边和上边的 pageX 和 pageY 的距离
获取移动时距离页面左边和上边的 pageX 和 pageY 的距离

用移动时的位置减去按下时的位置,就能获取到鼠标从元素原位置移动当前位置的距离,最要还要加上元素原位置的位置才是元素的实际位置

图示如下,有些简单,望见谅。

在这里插入图片描述

Q:为什么在最后赋值元素位置的时候要加上元素的初始位置 ?
A:如果不加上元素的初始位置的话,那么元素移动的位置始终是鼠标移动的距离,而不是元素从原位置移动到目标位置的距离

Q:为什么在移动中赋值了元素位置,还要在 mouseup 的时候重新赋值元素位置 ?
A:因为在移动中赋值元素位置的目的是让元素跟着鼠标走,而在 mouseup 事件触发的时候,就代表用户已经确定了移动元素的终点,这个时候不仅要赋值移动位置的距离,还要把当前位置保存到元素的原位置中。

总结

以上就是今天要分享的内容,本文简单介绍了 自定义拖拽组件位置 ( 如您发现本文代码的逻辑异常、或文章表述不清等问题,敬请留言或私信 ♥♥♥ )

接下来我们会逐步去实现针对拖拽组件的设置样式、设置属性、绑定事件等操作

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/12097.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

高空抛物检测方案设计(使用SOM进行轨迹分类)

文章目录前言一、技术方案介绍1.方案设计图2.流程介绍3.说明二、实际检测1.摄像头的安装2.实际检测三、误报解决误报事件1&#xff1a;飞鸟误报事件2&#xff1a;树叶误报事件3&#xff1a;被子解决方案轨迹展示原始轨迹SOM分类结果总结前言 高空抛物检测的关键是方案的设计&a…

MATLAB 绘图合集:分类散点图gscatter

本文主要介绍如何依据数据类别同时呈现数据散点图。 目录 说明 例子 使用默认设置绘图 使用数据类别来画图 使用多组数据类别来画图 创建并调整散点图 说明 gscatter(x,y,g) 创建 和 的散点图x&#xff0c;y按 分组g。输入x和 y是相同大小的向量。 例子 gscatter(x,y,g,c…

Jekyll 语句语法、功能的实现方法和结构介绍小手册

本文很长&#xff0c;建议使用侧边栏进行跳转。 本文虽然按照由浅入深的顺序介绍了 Jekyll 的语句语法和功能结构&#xff0c;但是主要用于快速查询一些 Jekyll 语句、功能的实现方法和结构介绍。 如果你想&#xff1a; 了解查看一些常用的 Jekyll 的相关命令和选项还请查看&…

第六章第一节:图的基本概念和存储及基本操作

文章目录1. 图的基本概念1.1 图逻辑结构的应用1.2 无向图&#xff0c;有向图1.3 简单图&#xff0c;多重图1.4 顶点的度&#xff0c;入度和出度1.5 顶点和顶点的关系描述1.6 连通图&#xff0c;强连通图1.7 研究图的局部——子图1.8 生成树1.9 生成森林1.10 边的权&#xff0c;…

手机怎么实现图片转文字操作?学会这三招就够了

随着互联网不断发展&#xff0c;我们手机的功能越来越丰富&#xff0c;现在基本上只需要一部手机&#xff0c;就能满足我们的多样化需求。比如想要提取书本中的文字&#xff0c;我们不需要再手动输入&#xff0c;只要将文字内容拍照下来&#xff0c;利用一些软件来提取图片中的…

【深度学习】argparse模块一些学习心得体会(2)| parser.parse_known_args() |位置参数 可选参数

文章目录前言一、位置参数和可选参数二、parser.parse_known_args()前言 之前我们写了一期命令行模块的使用介绍&#xff0c;但是依然有很多语法是没有介绍到的&#xff1a;比如parser.parse_known_args()这样的命令。这样的命令大量出现在各大开源项目中&#xff0c;今天我就…

外汇天眼:官网虚假宣传受到多重监管!FCA率先发出警告!

11月16日&#xff0c;英国金融行为监管局( FCA )发出最新警告&#xff0c;提示投资者警惕与 Trade Top FX 这家未经授权的公司打交道。 FCA警告称&#xff1a;Trade Top FX 在未经我们授权的情况下在英国提供金融服务或产品&#xff0c;如果一旦遭受资金损失&#xff0c;您将无…

Word处理控件Aspose.Words功能演示:在 Python 中比较两个 Word 文档

在各种情况下执行 Word 文档的比较以确定差异。各种在线工具允许您比较 Word 文档&#xff0c;但是&#xff0c;您可能需要在应用程序中实现比较功能。为实现它&#xff0c;本文展示了如何在 Python 中比较两个 Word 文档。 Aspose.Words for . Python 最新下载&#xff08;qu…

子容器在启动期间失败

​ 遇错&#xff1a; 今天遇到一个Bug&#xff1a;A child container failed during start 中文翻译为&#xff1a;子容器在启动期间失败 事情是这样的&#xff0c;一开始我在做案例的时候使用的tomcat是本地集成的&#xff0c;项目能正常运行&#xff1b; 后来我将tomcat的…

Dopamine-PEG-N3,DOPA-PEG-azide,水溶性PEG试剂供应

1、名称 英文&#xff1a;Dopamine-PEG-N3&#xff0c;DOPA-PEG-azide 中文&#xff1a;多巴胺-聚乙二醇-叠氮 2、CAS编号&#xff1a;N/A 3、所属分类&#xff1a;Azide PEG Dopamine PEG 4、分子量&#xff1a;可定制&#xff0c;有2k、5k、3.4k、10k、20k、1k 5、质量…

【salesforce】Lightning Web Component Study Log —— Part 2

持续学习中… 文章目录版本管理V1.0.01. The Lightning Web Components Model1.1 Web演变1.2 LWC1.3Aura和LWC的互用性2. Creating Lightning Web Components2.1 在VSCode定义一个LWC组件2.2 文件构成2.3 客户端-服务端体系结构2.4 基础UI组件-JS2.5 基础UI组件-HTML2.6 基础UI…

艾美捷PEG-2000 DMG解决方案

艾美捷PEG-2000 DMG英文全名1,2-dimyristoyl-rac-glycero-3-methoxypolyethylene glycol-2000&#xff0c;中文名可对应翻译为二肉豆蔻酰甘油-聚乙二醇2000。它的分子式为C122H242O50&#xff0c;分子量2509.2&#xff08;平均值&#xff09;&#xff0c;代表结构如下&#xff…

数字IC手撕代码-XX公司笔试真题(串并转换控制)

前言&#xff1a; 本专栏旨在记录高频笔面试手撕代码题&#xff0c;以备数字前端秋招&#xff0c;本专栏所有文章提供原理分析、代码及波形&#xff0c;所有代码均经过本人验证。 目录如下&#xff1a; 1.数字IC手撕代码-分频器&#xff08;任意偶数分频&#xff09; 2.数字…

JetsonNano部署yolo5 c++ onnx

编译OpenCV最新4.5.x版本 Jetson Nano自带的OpenCV版本比较低&#xff0c;Jetpack4.6对应的OpenCV版本为4.1的 而OpenCV当前最新版本已经到了4.5跟4.6了&#xff0c;4.5.x中OpenCV DNN支持了很多新的模型推理跟新的特性都无法在OpenCV4.1上演示&#xff0c;所以我决定从源码编…

Go语言 02

2.1 下载安装 Go Golang 中文网 Go 的安装也是十分的简洁、简单。就是 Next。哈哈 ~ 输入 go version 来查看是否安装完成吧 ~ 2.2 配置环境变量和工作目录 GOROOT&#xff1a;C:\Environment\Go 意味着 Go 在哪里。 在配置 GOPATH 之前&#xff0c;先把用户的 GOPATH 删掉。…

远程预付费电能管理系统在工业园的应用,主要功能有哪些?

安科瑞 李可欣 具体可咨询&#xff1a;Acrel_lkx 0概述 本项目为凌云工业园提供解决方案。本项目共有DDSY1352-NK/DTSY1352-NK预付费计量表41台&#xff0c;针对凌云工业园商业用电实现用电的智能化管理&#xff0c;通过安科瑞终端预付费电能表计来计量每个商铺的用电量&am…

web前端-html-css-background背景(color样式,image图片,repeat重复方式,position定位,简写)

背景 背景样式和背景图片重复方式 <!DOCTYPE html> <html><head><meta charset"utf-8" /><title></title><style type"text/css">.box1 {width: 1024px;height: 724px;margin: 0 auto;/*设置背景样式*/backgr…

Go-Zero环境搭建

Go-Zero环境搭建go-zero 需要安装的组件1 Protobuf下载安装需要先下载protoc执行器2 Protobuf下Go、GRPC插件的安装3 goctl 安装go-zero 需要安装的组件 protocprotoc-gen-goprotoc-gen-go-grpcgoctl 1 Protobuf下载安装 需要先下载protoc执行器 到 https://github.com/pro…

磷酸酶、转录因子、KRAS ——“不可成药”靶点?

不可成药的三大类靶点 传统的药物靶点绝大部分是具有适合的结合位点和明确的活性位点的蛋白质。药物分子往往通过“占位驱动”的药理学作用模式发挥作用。这种方法虽然可行&#xff0c;但并不能适用于所有的靶蛋白&#xff0c;尤其是在蛋白本身缺乏相应的结合口袋、蛋白的内源性…

天然产物化合物库

据文献报道&#xff0c;在传统药物研发时代&#xff0c;天然产物是新药研发最重要的、也几乎是独有的源泉&#xff0c;当时超过 80% 的药物均是原始天然产物或者其类似物&#xff1b;随着有机化学、药物化学、计算机技术等的发展&#xff0c;即使到了人工合成小分子药物占主导地…