探索可拖拽自定义看板

由 漆黑菌 于 2020年06月04日 发布

需求

某客户可能需要一个功能更强大自定义程度更高的看板,能够根据一定的规则(如尺寸,图表类型)进行看板的自定义。

参考

效果图+在线链接

demo 自定义看板效果图

如何实现

首先要有棋盘格子,其次要有矩形,最后把矩形放进棋盘中。矩形放入棋盘分两种情况,一种是插入一个全新矩形(比如从模板区添加新矩形到棋盘),另一种是把一个原有的矩形移动到新的位置(比如棋盘内拖拽矩形到新位置和矩形调整尺寸大小)。

布局

说到棋盘布局,首先想到的就是grid

    grid-template-rows: repeat(6, 120px); // 行数+高
    grid-template-columns: repeat(8, 120px); // 列数+宽
    gap: 10px; // 矩形间距
    grid-area: 1 / 3 / span 3 / span 4; // x,y,高,宽

这次实现棋盘背景用的就是grid,但是矩形排布部分用的还是absolute+translate的组合。

    transform: `translate(${(chart.startCol-1)*(baseLength+baseMargin)}px, ${(chart.startRow-1)*(baseLength+baseMargin)}px)` // x * 间距+宽 y * 间距+高

后续有可能的话想试着改成grid布局,根据caniuse上的信息其实现在grid支持程度已经很喜人了。

处理拖拽

用原生Drag API有一个好,拖拽过程中的预览浏览器会帮你实现。虽然不太好使吧,但写demo的时候能稍微偷点懒,所以这部分实现是没有的,直接跳过进入处理拖拽结束后放在哪这个问题。

从模板区拖拽矩形进入棋盘区

监听模板的dragstart事件,在全局记录pageX和pageY,监听dragend事件,记录pageX和pageY,计算出拖拽结束后模板的位置。

此处dragend事件跨浏览器表现不一致,不能正确获取pageX和pageY的参数,需要在document上监听dragover,并在全局记录。

然后使用getBoundingClientRect获取棋盘的left和top,经过计算后取整就能得到拖拽后模板的x和y,此处要注意边界检测,不能拖到棋盘外。之后就可以根据模板中包含的宽高信息(之后业务中应该还会包括图表类型信息)和x,y在棋盘区中创建新的矩形。

在棋盘区内拖拽

监听矩形的位置和监听模板方法类似,不过因为已经在棋盘内部了,根据位移即可计算出拖拽结束后矩形的位置。

经过一番处理后,已经知道了矩形要放到棋盘区哪个位置,下一步就是怎么把矩形科学的放入棋盘区。

如何检测碰撞

笨办法,棋盘就是笛卡尔坐标系,上面的点分为三种,被矩形占据的,没有被矩形占据的,在当前棋盘外的。

  1. 发现矩形在棋盘外的话就递归和+=1把棋盘尺寸变大,实现棋盘自动扩大功能。
  2. 发现矩形在没被占据的空间,就把矩形塞进去。
  3. 发现矩形在被占据的空间,就要进行碰撞后重新排列了。

如何处理碰撞

碰撞后排序处理其实是参考的Azure,有两个特征,首先是排序只在y轴方向发生,x轴不参与,其次就是在y轴上的排序一定是最紧凑的,不会出现空当。

  1. 当检测到一个新矩形进入棋盘后,被碰撞的旧矩形就需要在y轴坐标上递归+=1,直到不再和新矩形碰撞。而旧矩形被挤走后相对于其他矩形,也就变成了新矩形,递归以上处理即可。
  2. 矩形数组按照先x轴后y轴排序后,再递归加y轴-=1,检测出不会碰撞情况下各矩形在y轴方向最小值。

反面经验

  1. 尽量用mousedown和mouseup,而不是HTML5的Drag API,不仅兼容性差能抄的东西也没前者多,实际上主流的拖拽实现也是基于前者实现的。
  2. 在表达一个矩形在一个笛卡尔坐标系中状态的经典组合,x,y,长,宽的基础上务必,加上唯一标识ID。在寻找被遮挡的矩形和添加transition动画的时候会非常有用。
  3. 描述坐标位置时还是请老老实实从0开始,涉及到计算时从0开始从1切换没意义且痛苦。
  4. 发现矩形碰撞后的重新排序方法是个大坑,目前的实现基本是自己实现的有很多问题,费力不讨好。实际上大可不必在这方面死脑细胞,能抄就抄。
  5. draggable属性为false时,并不会阻止内部的文字等可拖拽内容继续触发dragstartdragend等事件,所以要给相关处理函数加上是否处在可以编辑状态的检测。

补充

发现了一个很合适的第三方库vue-grid-layout,之后vue的拖拽功能都可以基于此制作。