从零实现一套低代码(保姆级教程) --- 【3】实现Button组件和画布区的拖拽

摘要

本篇是这个系列的第三篇,如果你是第一次看到这个文章,那你应该会对低代码有那么一丢丢兴趣或者很有兴趣。从标题来看,也知道我这个系列就是实现一个低代码的项目。

那如果你想知道,我实现的项目的样子是什么样的,可以访问下面的链接: ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
XinBuilder 点击跳转

如果你有兴趣,跟着实现这么一套代码,那么你可以从第一章节开始阅读: 从零实现一套低代码(保姆级教程) --- 【1】初始化项目,实现左侧组件列表

因为上一篇文章,没有额外的提交,所有的代码都写在了文章里,所以这里就直接继续上一篇开始说。

在上一篇结束后,我们实现了左侧组件拖拽到画布区的效果!!! 但是因为我们组件里面只有一个文本,所以今天我们主要来实现组件,以及画布区中组件的拖拽。

1.组件的实现

在上一节中,我们在Button组件中,从props里面接收了style,从而实现组件在画布区的定位效果。

但是之所以显示文本,是因为我们写的就是文本,现在我们给它改成antD里面的Button组件。

javascript 复制代码
import { Button as AntButton } from 'antd'

export default function Button(props: any) {
  const {style} = props
  return (
    <div style={style}>
      <AntButton>按钮</AntButton>
    </div>
  )
}

这里注意一下,因为我的组件就叫Button,如果引入antD的Button时,没有改名会引起报错 。 你可以有两种解决办法,一种是像我这样,另一种就是修改自己这个组件的名,例如不叫Button,叫XButton即可

当你修改完后,再拖拽组件,那么在画布区显示的就不是一段文本了!!!!

2.实现画布区的拖拽

现在呢,我们拖拽到画布区的组件,就定死在那里了,不能拖拽,现在我们要给画布区的组件添加拖拽的效果。

现在请读者打开mainPart下的index.jsx文件,看一下最终的return。

我们在遍历comList的时候,返回的Com外层应该包一层div用来实现拖拽的效果。

同时也要给这个外层的div一个拖拽的方法,onDragStart,由于在画布区拖拽组件,也只能拖拽到画布区,所以我们不需要像上次一样,做一些禁止默认行为的处理了。

javascript 复制代码
  // 画布区的组件拖拽方法
  const onDragStart = (com: ComJson) => {
    return () => {

    }
  }


  return (
    <div onDrop={onDrop} onDragOver={onDragOver} onDragEnter={onDragEnter} className='mainCom'>
      {
        comList.map(com => {
          const Com = components[com.comType as keyof typeof components];
          // 在外面包一层控制拖拽的div
          return <div draggable onDragStart={onDragStart(com)}>
            <Com style={com.style} />
          </div>
        })
      }
    </div>
  )

OK。现在请打开浏览器看一下实现的效果吧,是不是发现有点问题,每次你在画布区拖拽后,都会生成一个新的组件,而不是组件的移动。这是为什么呢?

答案在我们写的onDrop方法里,因为每次拖拽完都会触发onDrop方法,每次都会push一个新的组件

所以我们应该是从左侧组件列表拖拽的时候,进行push。如果是在画布区拖拽,我们只需要更新style属性即可。

那怎么判断是画布区拖拽的组件还是左侧栏拖拽的组件呢?我们可以在这里的onDragStart里,将window.nowCom设定为一个特殊的值。在onDrop根据这个值去判断走哪条分支。

javascript 复制代码
  // 用于保存当前画布区拖拽的节点
  const [dragCom, setDragCom] = useState<ComJson | null>(null)

  const onDrop = (e: any) => {
    const endLeft = e.clientX;
    const endTop = e.clientY;
    const style = {
      position: 'absolute',
      left: endLeft + 'px',
      top: endTop + 'px',
      zIndex:100
    }
    // 判断当前拖拽的节点是从画布区拖拽,还是左侧组件栏拖拽
    if(window.nowCom === 'renderCom' && dragCom) {
      dragCom.style = style
    }else{
      comList.push({
        comType: window.nowCom,
        style
      })
    }
    setComList([...comList])
  }
  
  const onDragStart = (com: ComJson) => {
    return () => {
      // 设置拖拽的节点和nowCom的固定值
      window.nowCom = 'renderCom';
      setDragCom(com)
    }
  }

现在请再看一下效果,在画布区的拖拽就已经解决了。 但是呢,是不是位置不太准确呢

3.计算组件拖拽后的位置

好像在画布区拖拽完组件,新的位置总是差了那么一点,这是为什么呢? 先给一点提示,如果你拖拽的时候,鼠标位置在组件的左上角,那就没有问题了。

答案是,你给组件的left和top的定位,是基于鼠标位置的,也就是说,你把鼠标距离顶部和左部的距离,给了组件,那如果组件有自己的宽高,那么这个定位就是不准的。

所以为了算出正确的位置,我们应该计算出鼠标向左移动的距离和向上移动的距离。

然后加上组件本身的left和top值。
组件位置.left = 组件旧位置.left + 鼠标向左移动的距离
组件位置.top= 组件旧位置.top+ 鼠标向上移动的距离

那怎么计算鼠标移动的距离呢?我们可以在dragStart中,保存鼠标的位置。drop事件里,再保存一下鼠标的位置,诶?两者的差值不就是鼠标移动的位置嘛? 简单画一下流程图:

javascript 复制代码
  // 用来保存鼠标的开始位置和结束位置
  const distance = useRef<Distance>({
    startLeft: void 0,
    startTop: void 0,
    endLeft: void 0,
    endTop: void 0
  })
  
  const onDragStart = (com: ComJson) => {
    return (e: any) => {
      window.nowCom = 'renderCom';
      setDragCom(com);
      // 开始位置
      distance.current.startLeft = e.clientX;
      distance.current.startTop = e.clientY;
    }
  }

    const onDrop = (e: any) => {
    // 鼠标的结束位置
    distance.current.endLeft = e.clientX;
    distance.current.endTop = e.clientY;
    let style: any;
    if(window.nowCom === 'renderCom' && dragCom && dragCom.style) {
      // 根据鼠标位置的差值计算组件位置
      dragCom.style = {
        ...dragCom.style,
        left: parseInt(dragCom.style.left) + (e.clientX - (distance.current.startLeft || 0)) + 'px',
        top: parseInt(dragCom.style.top) + (e.clientY - (distance.current.startTop || 0)) + 'px'
      }
    }else{
      style = {
        position: 'absolute',
        left: distance.current.endLeft + 'px',
        top: distance.current.endTop + 'px',
        zIndex:100
      }
      comList.push({
        comType: window.nowCom,
        style
      })
    }
    setComList([...comList])
  }

基于上面的实现,我们就完成了组件在画布区的拖拽了。

本章内容会提交在github上:
github.com/TeacherXin/...
commit: 第三节:实现Button组件和画布区的拖拽

如果可以的话,可以给博主的GitHub点亮一颗小星星(╹▽╹)

博主补充

那如果现在你已经完成了所有的过程,你可以自己把其他组件的代码也补充一下。 我会在github上提交一段代码,用来补充文本框组件的代码,内容可以在github上查看

github.com/TeacherXin/...
commit: 第三节:实现Input组件代码

第二个问题,为什么鼠标的位置我们通过useRef来进行存储。因为这四个变量不需要更新去渲染组件,所以没必要通过useState去管理。但是呢,每次更新组件的时候还需要拿到之前的值,所以我们使用useRef进行管理。

第三个问题,现在我们有comList保存所有的组件数据,dragCom保存在画布区拖拽的组件。这个在后面,我们会采用redux进行管理,会有所更改,目前的话我们先使用这种模式来把整个流程串通。

最后,博主的TS可能没有那么熟练,如果有建议的话,博主也会积极采纳的!!!!

相关推荐
wakangda10 小时前
React Native 集成原生Android功能
javascript·react native·react.js
breaksoftware12 小时前
低代码开源项目Joget的研究——基本概念和应用
低代码
秃头女孩y16 小时前
【React中最优雅的异步请求】
javascript·vue.js·react.js
前端小小王1 天前
React Hooks
前端·javascript·react.js
迷途小码农零零发1 天前
react中使用ResizeObserver来观察元素的size变化
前端·javascript·react.js
算法小白(真小白)1 天前
低代码软件搭建自学第二天——构建拖拽功能
python·低代码·pyqt
码农君莫笑1 天前
信管通低代码信息管理系统应用平台
linux·数据库·windows·低代码·c#·.net·visual studio
不是鱼1 天前
构建React基础及理解与Vue的区别
前端·vue.js·react.js
低代码布道师1 天前
从用户视角出发:用例图分析家政预约小程序
低代码·小程序
飞翔的渴望1 天前
antd3升级antd5总结
前端·react.js·ant design