业务背景
目前产品中需要添加这样一个功能: 在页面侧边栏有两种不同的元素,外层元素和内部元素,页面右侧为画布区域,需要在左侧拖拽外层元素放置入画布,可以进行拖拽,移动,缩放,框选,对齐 等功能,拖拽内部元素需要不放在画布中,而是放入画布已经存在的外层元素中,并且可以在外层元素中进行拖拽上下排序
需求示意图
采用技术
- 项目采用技术栈 react
- 由于需要外层元素可以进行拖拽,移动,缩放,框选等功能,这里选用的是 react-moveable,此插件对于移动拖拽来说功能较为齐全好用
- 由于 moveable 本身不能对其内部元素有拖拽排序的功能,所以这里采用了 react-sortablejs 来开发内部元素放置如外层元素及在外层元素中拖拽上下排序的功能。(后期改用了 sortablejs,原因后序说明)
遇到的问题
- 在使用过程中发现,moveable 和 react-sortable 的各自的拖拽事件冲突,导致 react-sortable 在 moveable 中不起作用,拖拽不动,无法达到排序效果,即使设置 forceFallback 也不起作用 。
- 在查找 GitHub 的 issue 后发现 react-sortable 可能在对原框架封装上还是存在差异,改用原框架 sortableJs ,并将 forceFallback 设置为 true,(强制执行,将不使用原生的 html5 的拖放,可以修改一些拖放中元素的样式等,就可以内部拖拽排序了)
js
// 拖动配置
const ops = {
animation: 200, // 动画延迟
group: {
name: "groupName", // 拖拽组名称,用来和侧边栏拖入的元素配对,控制是否可放入
pull: false,
},
scroll: true,
className: "sortWarp",
multiDrag: true, // 开启多选
multiDragKey: "ctrl", // 多选按键
selectedClass: "sortable-selected", // 选择的类名
ghostClass: "sortable-ghostClass", // drop placeholder的css类名
dragClass: "gragClass",
filter: ".filterClass", // 过滤器,不需要进行拖动的元素
cancel: ".cancelClass",
forceFallback: true, // 忽略 HTML5拖拽行为,强制回调进行
fallbackTolerance: 3,
fallbackOnBody: false, // 是否将cloned DOM 元素挂到body元素上。
removeCloneOnHide: false, // Remove the clone element when it is not showing, rather than just hiding it
selected: true,
dataIdAttr: "data-id",
avoidImplicitDeselect: false, // true - if you don't want to deselect items on outside click
};
- 此时元素内部可以通过 sortable 进行排序,但是会导致拖拽过程中外层元素移动,原因是因为同时触发了外层元素的 moveable 拖拽事件,导致冲突 。
- 解决方案
- 在内部元素做了鼠标监听 ,移入移出内部元素时控制变量改变 moveable 状态,在移动到内部元素时禁用外层元素 moveable 拖拽,达到内部元素排序拖拽目的。移出后再开启。
- 解决方案
js
<Moveable
ref={moveableRef}
key={moveableKey} // 控制moveable key 强制更新
target={targets}
origin={false}
draggable={isContent ? false : true} // 移入内部元素时 禁止外层元素的拖拽事件
/>
- 此操作可行,但是在过程中发现问题
-
moveable 状态更新滞后,(移入移除改变变量 isContent 控制 moveable 状态,但是没有实时起作用,后给其设置 key 值一起控制,使其状态更新)
-
在外层元素拖拽或缩放过程中鼠标快速滑动,会触发内部元素禁止事件导致拖拽停止,并且卡顿
- 解决方案是监听在外层元素拖拽缩放过程中不触发鼠标事件 ,判断是否还是在拖拽或缩放,直到一个动作完成后才能再次触发内部事件,避免重复触发导致的状态不对或卡顿(卡顿发现是因为大量频繁触发鼠标事件导致,所以同样在排序元素内部做了监听,如果非排序状态才会触发事件)
-
js
<Moveable
ref={moveableRef}
key={moveableKey} // 控制 moveable key 强制更新
target={targets}
origin={false}
draggable={isContent ? false : true} // 移入内部元素时 禁止外层元素的拖拽事件
onDragStart={(e) => {
setIsDrag(true);
}}
onDragEnd={(e) => {
setIsDrag(false);
}}
/>
js
//在内部元素中
// 修改moveable禁用状态变量
const handleChangeMoveableStatus = (e, status, type) => {
e?.preventDefault();
e?.stopPropagation();
if (!isDrag && !isReSize) {
// 非正在拖拽外层或缩放外层状态
setisLayerContent(true);
};
}
- 解决后经测试不会再次卡顿,并且达成外层拖拽与内部排序不冲突的条件
- 成功实现外部元素拖拽及内部元素排序的互不影响~
后记
本文是作者对于个人的学习与总结笔记,如果有谬误的地方,欢迎各位提出并指正~