如何开发一个可以拖拽,可以resize的组件

1,096 阅读2分钟

PixPin_2024-06-24_12-03-29.gif 视频剪辑类工具常常需要这个样一个组件,如何用vue实现一个?

实现 resize

上面每一个小块我们称之为itemitem 左右各有一个holder ,item 在其父容器中是绝对定位(position: absolute)。

<div class="item"
  :style="{width: boundingRect.width + 'px', left: boundingRect.x + 'px' }">
  <div class="holder" @mousedown.stop="start($event, 0)"></div>
  <div class="box">
  </div>
  <div class="holder" @mousedown.stop="start($event, 1)"></div>
</div>

思路

捋一下思路,resize 分两种情况,拖左侧的holder,和拖右侧的holder。

拖右侧的holder:鼠标按下时记录一下当前item的宽高和位置信息,拖拽时改变itemwidth

拖左侧的holder:拖右侧的holder逻辑的基础上,再加一个逻辑,不断地改变itemleft

注意点

看上面的代码,并没有在 holder 上加 @mousemove 事件,如果依赖鼠标在 holder 上的 mousemove 事件,操作会很不流畅。因为鼠标必须先在holder上移动,才会有事件触发,item 的改变总是延迟一点;最要命的是如果鼠标移动稍微快一点,holder 没跟上,那后续事件就触发不了了。

所以应该把 mousemove 事件绑在 document 上,并在松开鼠标时解绑事件:

/**
 *  启动 resize
 * @param e 事件
 * @param holder 0 左边手柄; 1: 右边手柄
 */
function start(e, holder){
  document.addEventListener('mousemove', resize)
  document.addEventListener('mouseup', ()=>{
    document.removeEventListener('mousemove', resize)
  },{once: true})

  // 记录操作前item位置信息
  snapshot.cursorX = e.pageX // 鼠标位置
  snapshot.x = boundingRect.value.x
  snapshot.width = boundingRect.value.width
  snapshot.holder = holder
}

function resize(e) {
  // 鼠标位移
  const delta = e.pageX - snapshot.cursorX
  if (0 === snapshot.holder) {
    // 拉左侧手柄
    let newWidth = snapshot.width - delta
    newWidth = Math.max(newWidth, boundingRect.value.minWidth)
    model.value.duration = newWidth / timeline.scale

    const newX = snapshot.x + snapshot.width - newWidth
    model.value.start = newX / timeline.scale
  } else if (1 === snapshot.holder) {
    // 拉右侧手柄
    let newWidth = snapshot.width + delta
    newWidth = Math.max(newWidth, boundingRect.value.minWidth)
    model.value.duration = newWidth / timeline.scale
  }
}

碰撞检测

拖动holder往外拉,把item变宽的时候需要做一下碰撞检测,即拉左边的 holder 时,保证不能与前面一个 item 重叠;拉右边的 holder 时,保证不与后面一个item 重叠;拉第一个 item 的左侧 holder 时,left 不能小于 0。

后续

  1. 最顶部的时间标尺,用canvas实现,以提高性能
  2. Ctrl + 鼠标滚轮缩放比例尺