前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >vue 3.0 拖拽组件

vue 3.0 拖拽组件

作者头像
copy_left
发布2021-01-29 10:57:38
1.6K0
发布2021-01-29 10:57:38
举报
文章被收录于专栏:方球方球

拖拽容器

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

使用

代码语言:javascript
复制
// 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 拖拽点定义

代码语言:javascript
复制
type MovePoint = 'topLeft'
  | 'topRight'
  | 'middleLeft'
  | 'middleRight'
  | 'bottomLeft'
  | 'bottomRight'
  | 'middleTop'
  | 'middleBottom'

MoveBlock 容器参数

代码语言:javascript
复制
export interface MoveBlock {
  [s: string]: number
}

点位坐标

代码语言:javascript
复制
export interface Position {
  x: number
  y: number
}

StartState 鼠标触发时的初始状态

代码语言:javascript
复制
export interface StartState {
  preMoveBlock: MoveBlock
  type: MovePoint | ''
  startX: number
  startY: number
}

拖拽点事件函数

根据初始点位信息计算新坐标位置

代码语言:javascript
复制
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

代码语言:javascript
复制
<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

代码语言:javascript
复制
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
  }
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 拖拽容器
    • 使用
      • 参数
        • 事件
          • move hooks
            • MovePoint 拖拽点定义
            • MoveBlock 容器参数
            • 点位坐标
            • StartState 鼠标触发时的初始状态
            • 拖拽点事件函数
            • useMovePoint 拖拽点逻辑
          • useMoveBlock 拖拽容器逻辑
            • 参数
            • 周期事件
          • 源码
            • move.vue
            • hooks
        相关产品与服务
        容器服务
        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档