likes
comments
collection
share

【从零开始集成低代码平台】编辑器-组件旋转

作者站长头像
站长
· 阅读数 15

开源地址:github.com/zmkwjx/baik…

摘要

在几天前我们发布了一个关于组件拖拽的文章,上面讲述了关于实现拖拽功能的一系列心路历程。本想着拉伸和旋转功能一起做,但是发现如果要做拉伸就必须旋转功能先行实现,因为拉伸的坐标数据受旋转的角度和方向影响,所以不能以简单的坐标系来定论,需要建立函数体现,这个后面会再写文章说明。

在低代码编辑器中,组件旋转功能就是通过鼠标移动的方式,以某一点为中心进行旋转,来达到生产环境所需要的展示效果。

思考

  • 为什么不先实现拉伸功能再写旋转?

如果不考虑旋转功能,拉伸则只需要考虑某一轴的延展,通过改变某一轴的坐标点和组件宽度就可以达到目标。

【从零开始集成低代码平台】编辑器-组件旋转

但是这样会导致什么问题呢?如果组件被旋转了,我们在拉伸某个方向时,需要考虑的就不是某一个轴方向的问题了。我们需要找到参考坐标进行对比,需要根据参考坐标和旋转角度进行函数调整。如果旋转之后的组件按照单个轴方向进行拉伸,不建立参考系就会发生组件位置偏移的情况。

【从零开始集成低代码平台】编辑器-组件旋转

这样一来,如果要同时实现旋转和拉伸功能,势必需要先完成旋转功能,然后在组件可以旋转的条件下重新建立拉伸组件的算法。

  • 旋转组件功能的思路在哪里?

我们假设组件的中心为原点坐标,并由此建立坐标系,以画板的文档上方作为Y轴,文档右边作为X轴

【从零开始集成低代码平台】编辑器-组件旋转

我只需要记录鼠标从a点动到b点时,他们之间发生的旋转角度。当然这个方式有很多,因为我们会记录下每次的旋转角度,假设我们不发生旋转的角度为0,发生一个完整旋转的角度为360,那么意味着,我们不需要记录两个点的坐标,只需要记录每次发生坐标点距离y轴的角度,再不断修正差值,最后赋值就可以完成旋转功能了。

【从零开始集成低代码平台】编辑器-组件旋转

实现

关于技术架构和数据结构在组件拖拽部分以及有很详细的说明了,这部分不再论述。

具体编码

html 方面,我们给予每个组件一个旋转的标志,当组件被选中时,该标志浮现拖动该标志则可以发起旋转指令,并传递鼠标按下时的事件和组件数据。

<div v-if="item.selected" class="bk-drag-item-select">
  <div
    v-if="item.rotatable ?? true"
    class="rotate-circle"
    @mousedown.stop="handleMouseDown('rotate', $event, item)"
  />
</div>

hook 方面我设置三个方法去监听鼠标状态

// 开始监听旋转状态
const handleRotateStart = (event, item) => { ... }
// 监听旋转中的状态
const handleRotateStart = (event) => { ... }
// 结束旋转监听
const handleRotateStart = () => { ... }

handleRotateStart 旋转开始时,可以确定的一点是,拉伸和移动以及旋转事件不会同时发生,这个时候我们需要记录当前组件的相对原点,并且记录当前要旋转元素的键值,和所有需要旋转的元素

// 获取原点
logLocal.value.x = value.x + value.w / 2
logLocal.value.y = value.y + value.h / 2
// 筛出所有选中元素
logState.value = modelValue.value.filter(item => item.selected)
// 筛查当前选中元素
logIndex.value = modelValue.value.findIndex(item => item === value)

按下鼠标,开始移动鼠标,记录更新点,不断去修正角度差,保证每个元素都旋转相同的角度。由于差值可能会出现负数,我们需要调整角度使其在0~360之间。

// 获取旋转角度
const local = { x: event.pageX, y: event.pageY }
const rotate = calcAngle(logLocal.value, local)
// 获取旋转的差值
const moveR = rotate - modelValue.value[logIndex.value].r
// 赋值
for (let i = 0; i < logState.value.length; i++) {
    const r = logState.value[i].r + moveR
    logState.value[i].r = r > 360 ? r - 360 : r
}

其中 calcAngle 是最重要的函数,它会计算当前点和Y轴之间的夹角

const calcAngle = (a, b) => {
  const x = b.x - a.x
  const y = b.y - a.y
  const angle = Math.atan2(y, x) * (180 / Math.PI) + 90
  return Math.round(angle < 0 ? 360 + angle : angle)
}

Math.atan2()方法计算二维坐标系中任意一个点(x, y)和原点(0, 0)的连线与X轴正半轴的夹角大小。

Math.atan2()方法的原理:我们将X轴正半轴绕着原点旋转,直到它经过目标点(x, y);在这个旋转过程中,X轴正半轴扫过的角就是Math.atan2()方法要求的那一个角。

Math.round:四舍五入得到的角度值

鼠标放开结束监听,调用 handleRotateEnd 清空所有的状态记录

这部分只展示核心代码,具体代码在头部有贴出 github 的地址

开源地址:github.com/zmkwjx/baik…

结论

组件旋转的核心思路就是通过监听鼠标操作,鼠标按下时记录文档地址,计算出每次更新坐标距离Y轴的角度值,并通过这个角度值与组件当前角度进行差值比较,计算出每次移动的差值。