美文网首页
vue 3.0 拖拽组件

vue 3.0 拖拽组件

作者: copyLeft | 来源:发表于2021-01-25 09:32 被阅读0次

    拖拽容器

    12af53b2-4f10-48f0-85c4-061e86225d47.gif

    使用

    
    // 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
      }
    }
    
    

    相关文章

      网友评论

          本文标题:vue 3.0 拖拽组件

          本文链接:https://www.haomeiwen.com/subject/ysdbzktx.html