likes
comments
collection
share

【vue】聊一聊拖拽改变DOM大小的实现

作者站长头像
站长
· 阅读数 54

背景

大家或多或少应该有遇到过通过拖拽改变DOM大小的需求:比如说页面的侧边栏支持拖拽调整大小,使内容可视区变大。之所以讲这个需求,是因为在我个人开发的开源项目中有做到这个需求,所以在这里和大家聊一聊我的实现。以下内容均以向右拖拽为例。

思路

既然要实现拖拽,那就需要在DOM内有一个支持拖拽的节点,然后通过addEventListener来监听鼠标点击鼠标点击后移动事件鼠标松开事件。在鼠标点击后监听移动事件,在移动事件中计算鼠标点击位置与移动后的位置间的距离:这个距离就是DOM需要增加或者减少的宽度。

简单实现

思路清楚了,剩下的就是实现了,最主要的就是计算的部分了。贴上实现代码:

<template>
  <div class="container">
    <div>
      <div ref="box" class="box">
        <div ref="drag" class="drag"></div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount } from "vue"

const box = ref()
const drag = ref()

const width = ref()
const downPageX = ref()

const moveHandle = (event) => {
  document.documentElement.style.cursor = 'col-resize'
  const value = event.pageX - downPageX.value
  const size = width.value + value
  box.value.style.width = size + 'px'
}

const upHandle = () => {
  // 移除监听
  document.removeEventListener('mousemove', moveHandle)
  document.removeEventListener('mouseup', upHandle)
  // 移除鼠标样式
  document.documentElement.style.cursor = ''
}

const downListener = () => {
  drag.value.addEventListener('mousedown', (event) => {
    // 获取水平参数
    width.value = box.value.offsetWidth
    downPageX.value = event.pageX
    // 添加鼠标移动事件
    document.addEventListener('mousemove', moveHandle)
    // 添加鼠标松开事件
    document.addEventListener('mouseup', upHandle)
  })
}

onMounted(() => {
  downListener()
})

onBeforeUnmount(() => {
  // 移除监听
  document.removeEventListener('mousemove', moveHandle)
  document.removeEventListener('mouseup', upHandle)
  // 移除鼠标样式
  document.documentElement.style.cursor = ''
})

</script>

<style scoped>
// 省略...
</style>

拖拽指令

上面已经完成了简单的实现,但是在我们实际开发过程中不会就某一个地方需要用到这个实现。虽然可以CV,但这绝对不是最优解,在这基础之上进一步优化。

我们可以将这个功能封装成自定义指令,思路与上文的思路相差无几。在所有节点都挂载完成之后,在使用了指令的节点里面插入一个可拖拽的节点,然后通过addEventListener来监听各个鼠标事件,再进行一些计算,最后在节点卸载后移除相关监听。

本着精益求精的态度,必然不能只做到上面提到的那些。在实际开发中或许会遇到一些其它情况:比如我们拖拽的方向、可拖拽的最大最小距离、具体事件的回调等等问题都需要考虑在内。就有了以下完整的实现:

/**
 * @description: 拖拽改变大小指令
 * @param {*}
 * @return {*}
 * @author: gumingchen
 */
const horizontal = ['left', 'right']
const vertical = ['top', 'bottom']
const positions = [...horizontal, ...vertical]

// 默认参数
const defaultOptions = {
  // 拖拽位置
  position: positions[1],
  // 拖拽区域大小
  areaSize: '3px',
  // 拖拽区域背景色
  areaBackground: 'transparent',
  // Element的最小宽度/高度
  min: 200,
  // Element的最大宽度/高度
  max: 460,
  // 处理器
  downHandler: null,
  moveHandler: null,
  upHandler: null
}

// 初始化数据
let data = {
  el: null,
  dom: null,
  options: null,

  width: null,
  downPageX: null,
  height: null,
  downPageY: null
}

/**
 * 拖拽区域位置处理
 * @param {*} div
 * @param {*} position
 */
const positionHandle = (div, position, size) => {
  // 水平方向
  if (horizontal.includes(position)) {
    div.style.top = '0px'
    div.style.bottom = '0px'
    div.style[position] = '0px'
    div.style.cursor = 'col-resize'
    div.style.width = size
  }
  // 垂直方向
  if (vertical.includes(position)) {
    div.style.right = '0px'
    div.style.left = '0px'
    div.style[position] = '0px'
    div.style.cursor = 'row-resize'
    div.style.height = size
  }
}

/**
 * 创建节点
 * @returns Element
 */
const createElement = (options) => {
  const div = document.createElement('div')
  div.style.position = 'absolute'
  div.style.userSelect = 'none'

  const { position, areaSize, areaBackground } = options

  positionHandle(div, position, areaSize)

  div.style.background = areaBackground

  return div
}

/**
 * 鼠标移动监听事件
 * @param {*} event
 * @returns
 */
const moveHandle = (event) => {
  const { el, dom, options, width, downPageX, height, downPageY } = data
  const { moveHandler, position, max, min } = options
  // 事件回调
  if (typeof moveHandler === 'function') {
    moveHandler(event)
  }
  // 设置鼠标样式
  document.documentElement.style.cursor = dom.style.cursor
  // 水平方向处理
  if (horizontal.includes(position)) {
    const value = position === horizontal[0] ? downPageX - event.pageX : event.pageX - downPageX
    const size = width + value
    if (size >= max) {
      return
    }
    if (size <= min) {
      return
    }
    el.style.width = size + 'px'
  }
  // 垂直方向处理
  if (vertical.includes(position)) {
    const value = position === vertical[0] ? downPageY - event.pageY : event.pageY - downPageY
    const size = height + value
    if (size >= max) {
      return
    }
    if (size <= min) {
      return
    }
    el.style.height = size + 'px'
  }
}
/**
 * 鼠标松开监听事件
 * @param {*} event
 */
const upHandle = (event) => {
  // 事件回调
  const { upHandler } = data.options
  if (typeof upHandler === 'function') {
    upHandler(event)
  }
  // 移除监听
  document.removeEventListener('mousemove', moveHandle)
  document.removeEventListener('mouseup', upHandle)
  // 移除鼠标样式
  document.documentElement.style.cursor = ''
}

/**
 * 鼠标按下事件
 * @param {*} el 当前节点
 * @param {*} dom 可拖拽节点
 * @param {*} options 参数
 */
const downListener = (el, dom, options) => {
  dom.addEventListener('mousedown', (event) => {
    // 事件回调
    const { downHandler } = options
    if (typeof downHandler === 'function') {
      downHandler(event)
    }
    // 获取水平参数
    const width = el.offsetWidth
    const downPageX = event.pageX
    // 获取垂直参数
    const height = el.offsetHeight
    const downPageY = event.pageY
    // 设置数据
    data = { el, dom, options, width, downPageX, height, downPageY }
    // 添加鼠标移动事件
    document.addEventListener('mousemove', moveHandle)
    // 添加鼠标松开事件
    document.addEventListener('mouseup', upHandle)
  })
}

export default {
  mounted(el, binding) {
    el.style.position = 'relative'
    const { arg, value } = binding

    const options = value ? { ...defaultOptions, ...value } : { ...defaultOptions }
    if (arg) {
      options.position = arg
    }

    if (!positions.includes(options.position)) {
      console.warn(`[Directive warn]: Invalid arg: validation failed for arg. Expected one of ${ JSON.stringify(positions) }, got value "${ options.position }".`)
      return
    }

    const dom = createElement(options)

    downListener(el, dom, options)

    el.appendChild(dom)
  },
  unmounted() {
    // 移除监听
    document.removeEventListener('mousemove', moveHandle)
    document.removeEventListener('mouseup', upHandle)
    // 移除鼠标样式
    document.documentElement.style.cursor = ''
  }
}

以上就是本次分享的内容,附上源码

感谢看官看到这里,如果觉得文章不错的话,可以给小生的几个开源项目点个Star⭐!

转载自:https://juejin.cn/post/7281113851714781199
评论
请登录