VueDragResize遇到的坑及重写轮子

461 阅读4分钟

VueDragResize遇到的坑

使用场景

vue3+ts

现象

  1. 重置h和w的传参不起作用(偶尔)
  2. 直接用响应式数据作为初始化数据,然后resizing时候更新响应式数据会卡死(这个忘记怎么触发了,所以没调出来原因)
  3. 数据更新不同步;

原因

调试了一下源码。主要问题是在程序对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

支持功能

  1. 内容拖拽
  2. 内容缩放
  3. 设置比例(用于外层缩放后,拖拽的移速和缩放的速度及小块大小始终保持一致)
  4. 重置初始化数据

支持属性

属性说明类型默认值必传
h初始高度number50false
w初始宽度number50false
minH最小高度,缩放到这个高度后不会继续缩放number50false
minW最小宽度,缩放到这个宽度后不会继续缩放number50false
x初始离左偏移number0false
y初始离上偏移number0false
scale初始化缩放比例,如 0.5,小块会缩小 0.5 倍number1false
digits小数点后位数,如 2 时,返回数据为 100.11number0false
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缩放按钮的大小number8false

支持回调

回调说明回参
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传入都有提示 WX20221123-210035.png

组件的方法也有定义 WX20221123-210245.png

PS: template的写法,defineExpose可以把抛出的方法类型扔出。而tsx中使用expose是不能抛出类型的,查看了下vue3源码中的defineComponent声明类型,没有对expose做额外处理,这里想到了这样的办法去解决这个问题

WX20221123-210310.png

初始值和重置问题

组件初始化的时候根据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
  };
};

总结

  1. 源码地址,源码自取。放在了我之前一个项目的子项目里了。

  2. 目前没有放在npm上,要使用的话需要自己下下来添加到项目里(增加自己提交的代码量了!)

  3. 会去维护这个组件,如果遇到什么bug,有什么优化意见,想要添加什么功能(目前是自用所以把vue-drag-resize中很多没用到的功能都没有涉及到)都可以提issue或者在此评论。当然也可以自己修改,源码里的注释还是比较清晰的。