Drag and Drop
Drag and Drop Scaling A drag-and-drop component that works on both mobile and PC, consisting of two components: Draggable
and Droppable
.
- Draggable: Represents a draggable element.
- Droppable: Represents an area where draggable elements can be dropped.
To accommodate various use cases:
Draggable
can configure its drag type viatype
.Droppable
can configure allowed drag types viaacceptDragType
.Droppable
only acceptsDraggable
elements whosetype
is included in itsacceptDragType
.
Installation
INFO
If you need other components, refer toFull Components Installation
To install this component individually, run:
shell
npm install @havue/drag-and-drop --save
shell
yarn add @havue/drag-and-drop
shell
pnpm install @havue/drag-and-drop
Import
vue
<script>
import { HvDraggable, HvDroppable } from 'havue'
// or
import { HvDraggable, HvDroppable } from '@havue/components'
// or
import { HvDraggable, HvDroppable } from '@havue/drag-and-drop'
</script>
Example
- Left-side label blocks are Draggable elements, displaying their type and size.
- The gray Draggable element has
immediate="right"
, allowing instant dragging to the right. - Right-side contains two Droppable areas:
- Green labels can only be dropped in the green area.
- Yellow labels can only be dropped in the yellow area.
下列卡片可拖动:
yellow: 195 * 65
immediate: right
immediate: right
yellow: 177 * 55
green: 190 * 63
immediate: right
immediate: right
yellow: 197 * 68
yellow: 165 * 68
immediate: right
immediate: right
yellow: 167 * 83
yellow: 161 * 92
immediate: right
immediate: right
green: 190 * 91
green: 182 * 78
immediate: right
immediate: right
yellow: 165 * 58
green: 186 * 65
immediate: right
immediate: right
yellow: 169 * 53
yellow: 169 * 99
immediate: right
immediate: right
yellow: 161 * 75
green: 150 * 54
immediate: right
immediate: right
green: 170 * 58
yellow: 169 * 74
immediate: right
immediate: right
green: 198 * 70
green: 158 * 98
immediate: right
immediate: right
green: 198 * 72
green: 198 * 77
immediate: right
immediate: right
yellow: 151 * 82
yellow: 187 * 91
immediate: right
immediate: right
yellow: 191 * 93
yellow: 161 * 59
immediate: right
immediate: right
green: 188 * 56
green: 160 * 95
immediate: right
immediate: right
yellow: 153 * 64
green: 160 * 78
immediate: right
immediate: right
green: 200 * 77
yellow: 185 * 81
immediate: right
immediate: right
yellow: 197 * 71
yellow: 171 * 81
immediate: right
immediate: right
green: 158 * 66
yellow: 185 * 62
immediate: right
immediate: right
green: 166 * 94
green: 182 * 81
immediate: right
immediate: right
yellow: 173 * 69
green: 174 * 55
immediate: right
immediate: right
green: 160 * 99
下方为可拖放区域green:
下方为可拖放区域yellow:
Click to view code
vue
<template>
<div class="dnd-demo" ref="divRef">
<div class="left-box">
<p>下列卡片可拖动:</p>
<div class="top-drag-list-box">
<Draggable :type="item.type" v-for="(item, i) in DragItems" :data="item" :key="i" :immediate="item.immediate">
<div class="drag-box" :style="item.style">
{{ item.type }}: {{ `${item.width} * ${item.height}` }}
<br />
{{ item.immediate ? 'immediate: ' + item.immediate : '' }}
</div>
</Draggable>
</div>
</div>
<div class="right-box">
<p>下方为可拖放区域green:</p>
<Droppable
accept-drag-type="green"
@enter="
(type: DragAndDropDragType, point: DragAndDropPoint, data: any) => {
enteredType = 'green'
onEnter(type, point, data)
}
"
@move="onMove"
@leave="onLeave"
@drop="onDrop"
>
<div class="drop-box">
<div class="preview-box" v-if="enteredType === 'green'" :style="previewStyle"></div>
<div
class="inner-box-item"
v-for="item in greenInnerBoxList"
:key="item.key"
:style="{
top: item.y,
left: item.x,
width: item.width,
height: item.height
}"
></div>
</div>
</Droppable>
<p>下方为可拖放区域yellow:</p>
<Droppable
accept-drag-type="yellow"
@enter="
(type: DragAndDropDragType, point: DragAndDropPoint, data: any) => {
enteredType = 'yellow'
onEnter(type, point, data)
}
"
@move="onMove"
@leave="onLeave"
@drop="onDrop"
>
<div class="drop-box yellow">
<div class="preview-box" v-if="enteredType === 'yellow'" :style="previewStyle"></div>
<div
class="inner-box-item"
v-for="item in yellowInnerBoxList"
:key="item.key"
:style="{
top: item.y,
left: item.x,
width: item.width,
height: item.height
}"
></div>
</div>
</Droppable>
</div>
</div>
</template>
ts
<script setup lang="ts">
import type { DragAndDropPoint, DragAndDropDragType } from '@havue/drag-and-drop'
import { computed, ref, reactive } from 'vue'
// import { HvDraggable as Draggable, HvDroppable as Droppable } from '@havue/drag-and-drop'
import { HvDraggable as Draggable, HvDroppable as Droppable } from '@havue/components'
type BoxType = {
key: string | number | symbol
x: string
y: string
width: string
height: string
}
type EnteredType = 'green' | 'yellow' | ''
const divRef = ref<HTMLElement>()
const enteredType = ref<EnteredType>('')
const enteredPoint = ref({
x: 0,
y: 0
})
const previewData = ref()
const DragItems = reactive(
Array(40)
.fill(0)
.map((_, i) => {
let random1 = Math.round(Math.random() * 50)
let random2 = Math.round(Math.random() * 50)
return {
type: random1 % 2 === 0 ? 'green' : 'yellow',
width: random1 + 150,
height: random2 + 50,
immediate: i % 2 == 0 ? ('right' as const) : undefined,
style: {
background: i % 2 == 0 ? 'gray' : ''
}
}
})
)
const greenInnerBoxList = reactive<BoxType[]>([
{
key: 1,
x: '20px',
y: '20px',
width: '20px',
height: '20px'
},
{
key: 2,
x: '100px',
y: '30px',
width: '50px',
height: '200px'
},
{
key: 3,
x: '200px',
y: '40px',
width: '90px',
height: '40px'
}
])
const yellowInnerBoxList = reactive<BoxType[]>([
{
key: 4,
x: '200px',
y: '40px',
width: '90px',
height: '40px'
}
])
const previewStyle = computed(() => {
const preview = previewData.value || {}
return {
top: `${enteredPoint.value.y}px`,
left: `${enteredPoint.value.x}px`,
width: `${preview.width || 100}px`,
height: `${preview.height || 50}px`
}
})
function onEnter(type: DragAndDropDragType, point: DragAndDropPoint, data: any) {
enteredPoint.value = point
previewData.value = data
}
function onMove(type: DragAndDropDragType, point: DragAndDropPoint, data: any) {
enteredPoint.value = point
previewData.value = data
}
function onLeave() {
enteredType.value = ''
enteredPoint.value = { x: 0, y: 0 }
previewData.value = undefined
}
function onDrop(type: DragAndDropDragType, point: DragAndDropPoint, data: any) {
data = data || {}
const { width = 100, height = 50 } = data
if (enteredType.value === 'green') {
greenInnerBoxList.push({
key: Date.now(),
x: `${point.x - width / 2}px`,
y: `${point.y - height / 2}px`,
width: `${width}px`,
height: `${height}px`
})
} else if (enteredType.value === 'yellow') {
yellowInnerBoxList.push({
key: Date.now(),
x: `${point.x - width / 2}px`,
y: `${point.y - height / 2}px`,
width: `${width}px`,
height: `${height}px`
})
}
enteredType.value = ''
enteredPoint.value = { x: 0, y: 0 }
previewData.value = undefined
}
</script>
scss
<style lang="scss" scoped>
.dnd-demo {
display: flex;
width: 100%;
height: 700px;
overflow: auto;
background-color: rgb(20 177 177 / 41.6%);
p {
margin: 5px;
}
.left-box {
width: 30%;
height: 100%;
margin-right: 15px;
overflow: auto;
background-color: rgb(162 101 22 / 69.4%);
.top-drag-list-box {
display: flex;
flex-wrap: wrap;
gap: 20px;
justify-content: flex-start;
padding: 10px;
.drag-box {
width: 130px;
height: 50px;
color: white;
user-select: none;
background-color: black;
}
}
}
.right-box {
flex: 1;
.drop-box {
position: relative;
width: 400px;
height: 300px;
overflow: hidden;
background-color: rgb(11 104 28);
&.yellow {
background-color: rgb(188 149 21);
}
.preview-box {
position: absolute;
background-color: rgb(127 255 212 / 30%);
transform: translate(-50%, -50%);
}
.inner-box-item {
position: absolute;
background-color: rgb(143 10 65 / 46.6%);
}
}
}
}
</style>
Draggable Props
Property | Description | Type | Default |
---|---|---|---|
type | Drag element type | DragAndDropDragType | - |
immediate? | Direction where the element responds to drag immediately (default requires long-press) | ImmediateType | ImmediateType[] | - |
disabled? | Whether the element is disabled | boolean | - |
data? | Data associated with the draggable element (passed to Droppable) | any | - |
ts
type DragAndDropDragType: string | number | symbol
type ImmediateType = 'left' | 'right' | 'top' | 'bottom' | 'all' | undefined
Draggable Slots
Slot Name | Description |
---|---|
default | Default content |
drag-item | Element displayed during dragging |
Droppable Props
Property | Description | Type | Default |
---|---|---|---|
acceptDragType | Accepted drag types | DragAndDropDragType | Array<DragAndDropDragType> | - |
Droppable Events
Event | Description | Parameter Types |
---|---|---|
enter | Triggered when Draggable enters the area | (type: DragType, point: DragAndDropPoint, data: any) => void |
move | Triggered while Draggable moves within the area | (type: DragType, point: DragAndDropPoint, data: any) => void |
drop | Triggered when Draggable is dropped | (type: DragType, point: DragAndDropPoint, data: any) => void |
leave | Triggered when Draggable leaves the area | (type: DragType, data: any) => void |
ts
DragAndDropPoint: {
/** X-coordinate within Droppable */
x: string
/** Y-coordinate within Droppable */
y: string
}
Droppable Slots
Slot Name | Description |
---|---|
default | Default content |