拖拽容器
使用
// html
<Move :data='initSize' @update='update' >
  <div :style='style'></div>
</Move>
// ts
import { Move, MoveBlock } from './components/move'
export default defineComponent({
  components: {
    Move
  },
  setup(){
    const intiSize = ref({
      width: 100,
      heiht: 100
    })
    const style = computed(() => {
      const { width, height } = initSize.value
      return {
        width: `${width}px`,
        height: `${height}px`
      }
    })
    const update = (d: MoveBlock) => {
      initSiz.value = {
        width: d.width,
        height: d.height
      }
    }
    
    return {
      intiSize,
      style,
      update
    }
  }
})
参数
| 名称 | 
类型 | 
默认值 | 
说明 | 
| unit | 
string | 
px | 
单位 | 
| data | 
Obejct | 
{ width: 100, height: 100, top: 0, bottom: 0, left: 0, right: 0 } | 
初始位置及尺寸 | 
事件
| 名称 | 
说明 | 
参数 | 
备注 | 
| update | 
拖拽更新数据 | 
{ width, height, top, bottom, left, right } | 
 | 
move hooks
MovePoint 拖拽点定义
type MovePoint = 'topLeft'
  | 'topRight'
  | 'middleLeft'
  | 'middleRight'
  | 'bottomLeft'
  | 'bottomRight'
  | 'middleTop'
  | 'middleBottom'
MoveBlock 容器参数
export interface MoveBlock {
  [s: string]: number
}
点位坐标
export interface Position {
  x: number
  y: number
}
StartState 鼠标触发时的初始状态
export interface StartState {
  preMoveBlock: MoveBlock
  type: MovePoint | ''
  startX: number
  startY: number
}
拖拽点事件函数
根据初始点位信息计算新坐标位置
export interface MoveEvent {
  (diff: Position, start: StartState): MoveBlock
}
useMovePoint 拖拽点逻辑
封装各个拖拽点计算方法
参数
| 名称 | 
类型 | 
默认值 | 
说明 | 
| ctx | 
SetupContext | 
 | 
上下文环境 | 
| updateBlock | 
fn(d: MoveBlock):void | 
 | 
点位移动时触发更新函数 | 
周期事件
| 名称 | 
参数 | 
说明 | 
| pointMouseDown | 
startState | 
鼠标键按下, 返回初始状态 | 
| pointMouseMove | 
diff | 
鼠标移动, 返回计算后的插值 | 
| pointMouseUp | 
 | 
鼠标键抬起 | 
返回
| 名称 | 
类型 | 
说明 | 
| startState | 
StartState | 
鼠标按下时的初始状态 | 
| onPointMousedown | 
(e: MouseEvent, preMoveBlock: MoveBlock, t: MovePoint):void | 
鼠标按下后,触发拖拽监听 | 
useMoveBlock 拖拽容器逻辑
封装拖拽容易移动计算方法
参数
| 名称 | 
类型 | 
默认值 | 
说明 | 
| ctx | 
SetupContext | 
 | 
上下文环境 | 
周期事件
| 名称 | 
参数 | 
说明 | 
| blockMouseDown | 
moveBlock | 
鼠标键按下事件 容器初始状态 | 
| blockMouseMove | 
moveBlock | 
鼠标移动事件,容器状态 | 
| blockMouseDown | 
{ startX, startY } | 
鼠标键抬起事件, 当前鼠标位置 | 
返回
| 名称 | 
类型 | 
默认值 | 
说明 | 
| unit | 
Ref<string> | 
px | 
单位 | 
| moveBlock | 
Ref<moveBlock> | 
 | 
当前容器状态 | 
| moveBlockStyle | 
Computed<moveBlock> | 
 | 
容器计算样式 | 
| updateBlock | 
fn(d: MoveBlock) | 
 | 
容器状态更新 | 
| mouseMoveLock | 
fn(d: MovewBlock): MovewBlock | 
 | 
尺寸边界计算 | 
| onMouseDown | 
fn(e: MouseEvent) | 
 | 
拖拽监听触发 | 
源码
move.vue
<style lang='stylus' scoped>
$color = orange
.move{
  position absolute
  border 1px dashed transparent
  $pointSize=10px
  &:hover{
    border-color $color
    .move-point{
      opacity 1
    }
  }
  &-point{
    opacity 0
    position absolute
    width $pointSize
    height $pointSize
    border-radius $pointSize
    border 1px solid $color
    background white
    transition all .3s
    z-index: 1
    &:hover{
      transform scale(1.5, 1.5)
    }
  }
  $position = $pointSize/2 * -1
  .topLeft{
    top $position
    left $position
    cursor nw-resize
  }
  .topRight{
    top $position
    right $position
    cursor ne-resize
  }
  .middleTop{
    top $position
    left 0
    right 0
    margin auto
    cursor n-resize
  }
  .middleBottom{
    bottom $position
    left 0
    right 0
    margin auto
    cursor s-resize
    
  }
  .middleLeft{
    top 0
    bottom 0
    left $position
    margin auto
    cursor w-resize
  }
  .middleRight{
    top 0
    bottom 0
    right $position
    margin auto
    cursor e-resize
  }
  .bottomLeft{
    left $position
    bottom $position
    cursor sw-resize
  }
  .bottomRight{
    right $position
    bottom $position
    cursor se-resize
  }
}
</style>
<template>
  <div class='move' :style='moveBlockStyle' @mousedown.stop='onMouseDown' >
    <slot></slot>
      <div
      class='move-point'
      v-for='type of movePoints'
      @mousedown.stop='e => onPointMousedown(e, moveBlock, type)'
      :class='type'
      :key='type'>
      </div>
  </div>
</template>
<script lang='ts'>
import { defineComponent, PropType, watch } from 'vue'
import {
  useMoveBlock,
  useMovePoint,
  movePoints,
  MoveBlock
} from './hooks'
export default defineComponent({
  props: {
    unit: {
      type: String,
      default: 'px'
    },
    data: {
      type: Object as PropType<MoveBlock>,
      default: () => ({
        width: 100,
        height: 100,
        top: 0,
        bottom: 0,
        left: 0,
        right: 0
      })
    }
  },
  setup(props, ctx){
    const {
      unit,
      moveBlock,
      moveBlockStyle,
      updateBlock,
      mouseMoveLock,
      onMouseDown
    } = useMoveBlock(ctx)
    
    const update = (d: MoveBlock) => {
      updateBlock(mouseMoveLock(d))
    }
    const {
      onPointMousedown
    } = useMovePoint(ctx, update)
    watch(() => props.unit, () => {
      unit.value = props.unit
    }, { immediate: true })
    
    updateBlock(props.data)
    
    return {
      movePoints,
      moveBlockStyle,
      moveBlock,
      onPointMousedown,
      onMouseDown
    }
  }
})
</script>
hooks
import { ref, computed, SetupContext } from 'vue'
type MovePoint = 'topLeft'
  | 'topRight'
  | 'middleLeft'
  | 'middleRight'
  | 'bottomLeft'
  | 'bottomRight'
  | 'middleTop'
  | 'middleBottom'
export const movePoints: MovePoint[] = [
  'topLeft',
  'topRight',
  'bottomLeft',
  'bottomRight',
  'middleTop',
  'middleBottom',
  'middleLeft',
  'middleRight',
]
export interface MoveBlock {
  [s: string]: number
}
export interface Position {
  x: number
  y: number
}
export interface StartState {
  preMoveBlock: MoveBlock
  type: MovePoint | ''
  startX: number
  startY: number
}
export interface MoveEvent {
  (diff: Position, start: StartState): MoveBlock
}
export const topLeft: MoveEvent = (diff: Position, start: StartState) => {
  const { preMoveBlock } = start
  return {
    width: preMoveBlock.width - diff.x,
    height: preMoveBlock.height - diff.y,
    top: preMoveBlock.top + diff.y,
    left: preMoveBlock.left + diff.x,
  }
}
export const topRight: MoveEvent = (diff: Position, start: StartState) => {
  const { preMoveBlock } = start
  return {
    width: preMoveBlock.width + diff.x,
    height: preMoveBlock.height - diff.y,
    top: preMoveBlock.top + diff.y,
  }
}
export const middleTop: MoveEvent = (diff: Position, start: StartState) => {
  const { preMoveBlock } = start
  return {
    height: preMoveBlock.height - diff.y,
    top: preMoveBlock.top + diff.y,
  }
}
export const middleBottom: MoveEvent = (diff: Position, start: StartState) => {
  const { preMoveBlock } = start
  return {
    height: preMoveBlock.height + diff.y
  }
}
export const middleLeft: MoveEvent = (diff: Position, start: StartState) => {
  const { preMoveBlock } = start
  return {
    width: preMoveBlock.width - diff.x,
    left: preMoveBlock.left + diff.x,
  }
}
export const middleRight: MoveEvent = (diff: Position, start: StartState) => {
  const { preMoveBlock } = start
  return {
    width: preMoveBlock.width + diff.x,
  }
}
export const bottomLeft: MoveEvent = (diff: Position, start: StartState) => {
  const { preMoveBlock } = start
  return {
    width: preMoveBlock.width - diff.x,
    height: preMoveBlock.height + diff.y,
    left: preMoveBlock.left + diff.x,
  }
}
export const bottomRight: MoveEvent = (diff: Position, start: StartState) => {
  const { preMoveBlock } = start
  return {
    width: preMoveBlock.width + diff.x,
    height: preMoveBlock.height + diff.y
  }
}
export function useMovePoint(ctx: SetupContext, updateBlock: (d: MoveBlock) => void) {
  const eventMap: { [s: string]: MoveEvent } = {
    topLeft,
    topRight,
    bottomLeft,
    bottomRight,
    middleTop,
    middleBottom,
    middleLeft,
    middleRight,
  }
  const startState = ref<StartState>({
    preMoveBlock: {},
    type: '',
    startX: 0,
    startY: 0
  })
  const onPointMousedown = (e: MouseEvent, preMoveBlock: MoveBlock, t: MovePoint) => {
    startState.value = {
      type: t,
      preMoveBlock: { ...preMoveBlock },
      startX: e.clientX,
      startY: e.clientY
    }
    const cb = (event: MouseEvent) => {
      const { startX, startY, type } = startState.value
      const diff = {
        x: event.clientX - startX,
        y: event.clientY - startY
      }
      const setFn = eventMap[type]
      if (setFn) {
        updateBlock(setFn(diff, startState.value))
      }
      ctx.emit('pointMouseMove', { ...diff })
    }
    ctx.emit('pointMouseDown', { ...startState.value })
    document.addEventListener('mousemove', cb)
    document.addEventListener('mouseup', () => {
      document.removeEventListener('mousemove', cb)
      ctx.emit('pointMouseUp')
    })
  }
  return {
    startState,
    onPointMousedown
  }
}
export function useMoveBlock(ctx: SetupContext) {
  const unit = ref('px')
  const moveBlock = ref<MoveBlock>({
    width: 0,
    height: 0,
    top: 0,
    bottom: 0,
    left: 0,
    right: 0,
  })
  const updateBlock = (data: MoveBlock) => {
    const _data = { ...moveBlock.value, ...data }
    if (_data.width <= 0) {
      _data.width = 0
    }
    if (_data.height <= 0) {
      _data.height = 0
    }
    moveBlock.value = _data
    ctx.emit('update', { ...moveBlock.value })
  }
  // 防止尺寸为零时,元素移动
  const mouseMoveLock = (d: MoveBlock) => {
    const _d = { ...d }
    if (_d.width <= 0) {
      _d.width = 0
      _d.left = moveBlock.value.left
    }
    if (_d.height <= 0) {
      _d.height = 0
      _d.top = moveBlock.value.top
    }
    return _d
  }
  const moveBlockStyle = computed(() => {
    return Object.entries(moveBlock.value).reduce((acc, [key, val]) => {
      return { ...acc, [key]: `${val}${unit.value}` }
    }, {})
  })
  const prePosition = ref({
    startX: 0,
    startY: 0
  })
  const onMouseMove = (e: MouseEvent) => {
    const { startX, startY } = prePosition.value
    const diff = {
      x: e.clientX - startX,
      y: e.clientY - startY
    }
    prePosition.value = {
      startX: e.clientX,
      startY: e.clientY
    }
    const { top, left } = moveBlock.value
    updateBlock({
      top: top + diff.y,
      left: left + diff.x
    })
    ctx.emit('blockMouseMove', moveBlock.value)
  }
  const onMouseUp = () => {
    ctx.emit('blockMouseUp')
    document.removeEventListener('mousemove', onMouseMove)
    document.removeEventListener('moouseup', onMouseUp)
  }
  const onMouseDown = (e: MouseEvent) => {
    prePosition.value = {
      startX: e.clientX,
      startY: e.clientY
    }
    ctx.emit('blockMouseDown', { ...prePosition.value })
    document.addEventListener('mousemove', onMouseMove)
    document.addEventListener('mouseup', onMouseUp)
  }
  return {
    unit,
    moveBlock,
    moveBlockStyle,
    updateBlock,
    mouseMoveLock,
    onMouseDown
  }
}