拖拽容器
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
}
}