低代码平台拖拽功能的实现 | 青训营笔记

441 阅读4分钟

这是我参与【第四届青训营】笔记创作活动的第3天

低代码平台中的拖拽功能

低代码平台为开发者提供了便利的简单页面搭建功能,开发者只需拖拽需要的组件,手动完成布局,即可完成页面的搭建。因此拖拽功能就显得极其重要。

而我在此次项目中用到两种时机的拖拽功能

  1. 从左侧物料区拖到画布中的拖拽功能
  2. 在画布中调整组件位置的拖拽功能

从左侧物料区拖到画布中的拖拽功能

这部分主要靠h5提供的内置api--draggable属性 而要用好这个属性,首先得先知道一些概念

1,拖放事件 

如果不使用drag元素的话;也可以使用onmousedown,onmouseover,onmouseup这三个来实现效果;想要拖动的话,必须在其设置draggable属性为true;需要注意的是,img和a默认为true

拖放过程中,被拖动的元素被称为目标对象,在这过程中经历的其他对象称为过程对象,最终到达的对象接叫目标对象;而每个都有其属性

源对象

ondragstart - 用户开始拖动元素时触发
ondrag - 元素正在拖动时触发
ondragend - 用户完成元素拖动后触发

过程对象

ondragenter - 当被鼠标拖动的对象进入其容器范围内时触发此事件
ondragover - 当某被拖动的对象在另一对象容器范围内拖动时触发此事件
ondragleave - 当被鼠标拖动的对象离开其容器范围内时触发此事件

目标对象

ondrop - 在一个拖动过程中,释放鼠标键时触发此事件

注意: 在拖动元素时,每隔 350 毫秒会触发 ondrag 事件。

2,dataTransfer对象 

        setData(name,data) 存储数据
第一个参数,要存储数据的名字(可以自己命名)
第二个参数,要存储的数据

        getData(name) 获取存储名字为“name”的数据

        clearData(name) 清除dataTransfer存储的数据
参数可选,为空则清空所有数据

        setDragImage(element,x,y) 通过img元素设置拖放图标
element是拖拽时鼠标下面的图片(img或canvas元素)
x、y分别相对于图片的横向和纵向偏移量,对应鼠标指针

        files属性
返回被拖拽的文件列表,是一个FileList对象,有length属性,可通过下标访问

项目中实现

在项目中,我给左侧物料区每个组件的盒子添加上draggable属性,每个盒子就能拖拽了,再给画布组件editor添加@drop="handleDrop"事件,而且需要给添加 @dragenter="handleDragenter"`` @dragover="handleDragover"`` @dragleave="handleDragleave"事件并为期在每个阶段执行对应应该执行的代码

    handleDragenter(e) {
      e.dataTransfer.dropEffect = "move";
    },
    handleDragover(e) {
      e.preventDefault();
    },
    handleDragleave(e) {
      e.dataTransfer.dropEffect = "none";
    },

而要确定组件落入画布中的位置,可以先利用offsetY和offsetX粗略定大概位置,在之后用户稍作调整即可

handleDrop(e) {
      let position = {
        top: e.offsetY,
        left: e.offsetX,
      };
      // 之后的代码。。。
    },

然后通过dataTransfer对象的setDatagetData来进行传值

在画布中调整组件位置的拖拽功能

首先需要将画布设为相对定位 position: relative,然后将每个组件设为绝对定位 position: absolute。除了这一点外,还要通过监听三个事件来进行移动:

  1. mousedown 事件,在组件上按下鼠标时,记录组件当前的位置,即 xy 坐标(为了方便讲解,这里使用的坐标轴,实际上 xy 对应的是 css 中的 left 和 top
  2. mousemove 事件,每次鼠标移动时,都用当前最新的 xy 坐标减去最开始的 xy 坐标,从而计算出移动距离,再改变组件位置。
  3. mouseup 事件,鼠标抬起时结束移动。
handleMousedown(e, component) {
      // 获取组件起始位置
      let startY = parseInt(component.style.top);
      let startX = parseInt(component.style.left);
      // 获取组件起始距离屏幕的位置
      let { x, y } = e;
      // 防抖
      var timer = null;
      const move = (e) => {
        if (timer) return;
        timer = setTimeout(() => {
        // 组件当前位置 = 移动时距离屏幕的位置 - 起始距离屏幕的位置 + 组件起始位置
          let style = {
            top: `${e.y - y + startY}px`,
            left: `${e.x - x + startX}px`,
          };
          component.style = formatStyle({ ...component.style, ...style });
          timer = null;
        }, 16);
      };
      // 鼠标松开移除mousemove和mouseup事件
      const up = () => {
        document.removeEventListener("mousemove", move);
        document.removeEventListener("mouseup", up);
      };
    // 绑定mousemove和mouseup事件
        document.addEventListener("mousemove", move);
        document.addEventListener("mouseup", up);
    },

此过程中,组件当前位置 = 移动时距离屏幕的位置 - 起始距离屏幕的位置 + 组件起始位置 通过此关系来确保组件能跟随鼠标移动