VueDragResize遇到的坑
使用场景
vue3+ts
现象
- 重置h和w的传参不起作用(偶尔)
- 直接用响应式数据作为初始化数据,然后resizing时候更新响应式数据会卡死(这个忘记怎么触发了,所以没调出来原因)
- 数据更新不同步;
原因
调试了一下源码。主要问题是在程序对h,w,x,y初始值的监听
w: {
handler(newVal, oldVal) {
if (this.stickDrag || this.bodyDrag || newVal === this.width) {
return;
}
const stick = 'mr';
const delta = oldVal - newVal;
this.stickDown(stick, { pageX: this.right, pageY: this.top + this.height / 2 }, true);
this.stickMove({ x: delta, y: 0 });
this.$nextTick(() => {
this.stickUp();
});
}
},
h: {
handler(newVal, oldVal) {
if (this.stickDrag || this.bodyDrag || newVal === this.height) {
return;
}
const stick = 'bm';
const delta = oldVal - newVal;
this.stickDown(stick, { pageX: this.left + this.width / 2, pageY: this.bottom }, true);
this.stickMove({ x: 0, y: delta });
this.$nextTick(() => {
this.stickUp();
});
}
},
这里做了this.stickDrag || this.bodyDrag || newVal === this.height
判断,避免在不需要触发的时候触发下面的逻辑,然后下面调用了thi.stickDown方法,里面有一行代码是this.stickDrag = true
。
这下就理解了为什么有时候重置传参没有起作用的原因了,同时更新h和w,执行完h的watch后,由于stickDrag的释放是在nextTick中的,所以到w的监听时,stickDrag仍是true,那w的更新就不执行了。
然后const delta = oldVal - newVal;
也是存在问题的,如果oldVal不等于this.height,那效果就会存在问题,比如说我定义了个const height = ref(100)
我在resizing的时候没有把更新值实时给到height,我缩放到了200,我再给手动height.value = 200
,显示效果应该是不动?但是这段逻辑会导致真实大小是300。
解决
本来想提issue的,但是看作者好像很久没维护了,而且对ts支持也不好,所以自己写了一个符合自己应用场景的缩放和拖拽组件。
重写轮子
技术栈
vue3 + tsx + scss
支持功能
- 内容拖拽
- 内容缩放
- 设置比例(用于外层缩放后,拖拽的移速和缩放的速度及小块大小始终保持一致)
- 重置初始化数据
支持属性
属性 | 说明 | 类型 | 默认值 | 必传 |
---|---|---|---|---|
h | 初始高度 | number | 50 | false |
w | 初始宽度 | number | 50 | false |
minH | 最小高度,缩放到这个高度后不会继续缩放 | number | 50 | false |
minW | 最小宽度,缩放到这个宽度后不会继续缩放 | number | 50 | false |
x | 初始离左偏移 | number | 0 | false |
y | 初始离上偏移 | number | 0 | false |
scale | 初始化缩放比例,如 0.5,小块会缩小 0.5 倍 | number | 1 | false |
digits | 小数点后位数,如 2 时,返回数据为 100.11 | number | 0 | false |
nub | 总共八个位置的缩放按钮 | ("top" | "right" | "bottom" | "left" | "left_top" | "right_top" | "right_bottom" | "left_bottom")[] | ["top", "right", "bottom", "left", "left_top", "right_top", "right_bottom", "left_bottom"] | false |
nubSize | 缩放按钮的大小 | number | 8 | false |
支持回调
回调 | 说明 | 回参 |
---|---|---|
setSize | 大小变化时的回调 | Record<'x' | 'y' | 'h' | 'w', number> |
setPosition | 位置变化时的回调 | Record<'x' | 'y', number> |
支持方法
方法 | 说明 | 入参 | 返回值 |
---|---|---|---|
reset | 重置初始化参数 | 所有的 props,可选。如下 | void |
{
h?: number | undefined;
w?: number | undefined;
x?: number | undefined;
y?: number | undefined;
minH?: number | undefined;
minW?: number | undefined;
scale?: number | undefined;
digits?: number | undefined;
nub?: ("top" | ... 6 more ... | "left_bottom")[] | undefined;
nubSize?: number | undefined;
}
其他
ts支持
props传入都有提示
组件的方法也有定义
PS: template的写法,defineExpose可以把抛出的方法类型扔出。而tsx中使用expose是不能抛出类型的,查看了下vue3源码中的defineComponent声明类型,没有对expose做额外处理,这里想到了这样的办法去解决这个问题
初始值和重置问题
组件初始化的时候根据props传入的初始值初始化,然后不监听props中值的变化,重置需要使用reset方法重新传参。减少外层数据对组件中的影响。
事件绑定
参考了vue-drag-resize中的事件集。在使用vue-drag-resize发现他的操作还是很丝滑的,所以直接仿照了他绑定了事件
const documentFuncs = [
['mousedown', onStart],
['mousemove', onOperating],
['mouseleave', onEnd],
['mouseup', onEnd],
['touchstart', onStart],
['touchmove', onOperating],
['touchend', onEnd],
['touchcancel', onEnd]
] as const;
const addOrRemoveEvents = (isRemove = false) => {
const func = [
document.documentElement.addEventListener,
document.documentElement.removeEventListener
][Number(isRemove)];
documentFuncs.forEach((item) => func(item[0], item[1]));
};
onMounted(() => {
addOrRemoveEvents();
});
onUnmounted(() => {
addOrRemoveEvents(true);
});
缩放的处理
有八个缩放的方位,不想区分八种情况,所以枚举了一下不同和相同,然后做统一的逻辑处理。大致为top,right_top,left_top对于高度来说都是可以用有top的情况来处理的,其他同理。
对于缩放按钮的位置和鼠标样式处理也是相同的逻辑。(这里贴出的是样式的代码,易于理解上述内容)
export const nubStyles = (pos: NubTypeNames, size: number): StyleValue => {
const directions = ['top', 'bottom', 'right', 'left'].map((d) => pos.includes(d));
const [isTop, isBottom, isRight, isLeft] = directions;
const scale = `${size}px`;
const resizeDirs = 'nsew';
// 鼠标hover的样式,使用nsew字符串和directions数组对应
const cursor = `${directions.reduce(
(acc, is, index) => `${acc}${is ? resizeDirs[index] : ''}`,
''
)}-resize`;
return {
height: scale,
width: scale,
// 用top和transform控制位置,通过是否在哪个位置来计算对应的值
top: isBottom ? '' : isTop ? 0 : '50%',
bottom: isBottom ? 0 : '',
left: isRight ? '' : isLeft ? 0 : '50%',
right: isRight ? 0 : '',
transform: `translate(${isRight ? '50%' : '-50%'}, ${isBottom ? '50%' : '-50%'})`,
cursor
};
};
总结
-
源码地址,源码自取。放在了我之前一个项目的子项目里了。
-
目前没有放在npm上,要使用的话需要自己下下来添加到项目里(增加自己提交的代码量了!)
-
会去维护这个组件,如果遇到什么bug,有什么优化意见,想要添加什么功能(目前是自用所以把vue-drag-resize中很多没用到的功能都没有涉及到)都可以提issue或者在此评论。当然也可以自己修改,源码里的注释还是比较清晰的。