【手摸手实现可视化系统(三)】拖放组件到主舞台上

886 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

前言

本文是**手摸手带你实现可视化系统(高仿dataV)**的第三篇--拖放组件到主舞台上 一、 【可视化系统】如何实现编辑面板的动态缩放?

二、【可视化系统】如何实现组件的自由变换?

也欢迎持续关注我的专栏# 前端可视化 ,一起学习更多可视化领域知识。

通过之前两篇文章的介绍,我想你大概已经掌握了如何制作一个编辑面板(主舞台),和如何实现组件在编辑面板上自由变换(拖拽、缩放、旋转等)。

我们今天主要讲的是,如何从组件库中拖动一个组件到主舞台上,文字不好理解,我们直接看下最终要实现的效果。

chrome-capture-2022-4-28.gif

让我们奔着这个效果,来一步步实现。

通读本文你将收获:

  1. Html5 新属性draggable的使用方式
  2. drag相关事件和api
  3. 从零到一实现拖放的实践经验分享

需求分析

这个需求其实就是向主舞台添加组件,我最早实现的一版是点击组件图标,向舞台上添加组件数据,给组件一个初始位置。 这样做大概有两方面的问题:

  • 初始位置的定制上,不管放在哪都觉得不够优雅;
  • 其次编辑面板有层级的概念,后添加的组件会重叠的盖到先添加的组件之上

出于这样的考虑,感觉更优雅的方式就是把主动权交给使用方,主动拖动组件到合适的位置,然后释放。

那么接下来我们就明确需求了: 实现组件拖放到主舞台。

轮子&梯子

首先想到的就是sortableJS, 因为之前做list拖拽排序的时候有用到过这个三方库。感觉还是蛮强大的。

官方示例看了下,基本上都是和列表相关的,侧重于列表的移动和拖动排序,就像它的名字:sortable.

chrome-capture-2022-4-28 (1).gif

我们舞台上是有层级的概念的,这种挤压碰撞的效果显然不是我们想要的。

之前有了解到html5有支持最新的dragable属性,轻松实现拖拽,刚好尝试体验一下,所以,直接放弃了sortableJS,拥抱dragable.

拥抱dragable

学习api只需要两步:第一步搜索MDN,第二步官方demo跑一下。

提炼一下MDN的重要信息:

  1. dragable 属性(true/false)用来表示元素是否可被拖拽;
  2. 核心的事件有这么三个(dragstart\dragenter\drop)

image.png

  1. 拖放的目标位置元素需要阻止浏览器的默认事件
  2. 拖动完成需要完成数据传递(在事件回调event中保存)

开始撸代码

  <div
    class="chart"
    v-for="widget in group.widgets"
+    draggable="true"
    :key="widget.name"
    @click="UseIt(widget)"
+    @dragstart="e => dragstart(e, widget)"
+    @dragenter="dragenter"
  >
    <img class="chart-img" :src="widget.img" draggable="false" />
    <div class="chart-label">{{ widget.label }}</div>
  </div>

我们先是给组件添加了draggable="true",声明可拖拽; 然后绑定dragstart、dragenter事件。

我们这里给dragstart绑定了组件的数据,就是为了传递给主舞台。

dragstart(event, widget) {
  // 获取完整的组件属性
  const _widget = this.getWidgetData(widget);
  // event.dataTransfer.setData 方法,记录数据
  event.dataTransfer.setData("text/plain", JSON.stringify(_widget));
  // 代表拖拽的模式,copy代表复制
  event.dataTransfer.effectAllowed = "copy";
},

上面代码的注释已经说明了问题,对具体某个方法不明白可以看看mdn

到这里我们才算完成了一半,我们只做了 1.使组件图标可拖动; 2.拖动时绑定了数据。

我们还需要在组件拖动到主舞台上时,获取到组件数据,并在相应位置绘制出该组件。

主舞台的处理

  <div
    class="screen"
    ref="screen"
    :style="screenConfig"
    @click="loadSetting('screen')"
+    @dragover="dragover"
+    @drop="drop"
  >

还是添加两个事件回调,但是和组件上绑定的不一样哦~

   dragover(event) {
      // 阻止浏览器的默认事件
      event.preventDefault();
    },
    drop(event) {
      // 获取组件数据
      const data = event.dataTransfer.getData("text/plain");
      // 获取组件拖动到舞台上的相对位置
      const { layerX, layerY } = event;
      const config = JSON.parse(data);
      const widget = {
        ...config,
        posX: layerX / this.scale - (config.width || 0) / 2,
        posY: layerY / this.scale - (config.height || 0) / 2,
      };
      //添加组件到主舞台
      this.addElements(widget);
      event.preventDefault();
    },

获取组件拖动到舞台上的相对位置, 这个地方的layerX, layerY是拖动元素在目标元素上的相对位置,以左上角为坐标原点。

我们这里更新实际位置posX: layerX / this.scale - (config.width || 0) / 2,,解释一下这样处理的原因,因为我们的主舞台是经过 this.scale 倍缩放的,所以 layerX / this.scale才是实际在舞台上的位置,然后 -(config.width || 0) / 2,是为了以拖动位置为组件的中心点来渲染组件。

总结

以上,我们就完成了文章开头动图实现的拖放效果了。

系列小结: 目前我们已经完成了可视化系统最核心部分: 舞台搭建、组件拖放、组件变换。 紧接着我们需要完成组件配置表单部分,通过配置项动态生成表单供用户配置。

我将在最近完成这部分的工作并以文章的形式和大家交流,请持续关注~

更文不易,请多多点赞、收藏,这将是我持续创作的动力!