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:
Draggablecan configure its drag type viatype.Droppablecan configure allowed drag types viaacceptDragType.Droppableonly acceptsDraggableelements whosetypeis included in itsacceptDragType.
Installation
INFO
If you need other components, refer to Full Components Installation
To install this component individually, run:
shell
npm install @havue/drag-and-drop --saveshell
yarn add @havue/drag-and-dropshell
pnpm install @havue/drag-and-dropImport
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. - The yellow labels sets the drag-item custom slot display.
- 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.
Draggable list:
yellow: 193 * 94
immediate: right
immediate: right
green: 194 * 90
green: 194 * 96
immediate: right
immediate: right
green: 158 * 70
yellow: 167 * 81
immediate: right
immediate: right
yellow: 177 * 50
yellow: 185 * 52
immediate: right
immediate: right
green: 182 * 51
green: 164 * 78
immediate: right
immediate: right
green: 172 * 71
yellow: 159 * 55
immediate: right
immediate: right
yellow: 157 * 64
yellow: 181 * 97
immediate: right
immediate: right
green: 180 * 59
green: 178 * 56
immediate: right
immediate: right
yellow: 197 * 74
yellow: 187 * 89
immediate: right
immediate: right
green: 190 * 78
green: 178 * 81
immediate: right
immediate: right
yellow: 181 * 54
green: 160 * 74
immediate: right
immediate: right
yellow: 151 * 63
yellow: 163 * 78
immediate: right
immediate: right
yellow: 183 * 90
green: 192 * 59
immediate: right
immediate: right
green: 174 * 93
yellow: 197 * 73
immediate: right
immediate: right
green: 186 * 79
green: 184 * 89
immediate: right
immediate: right
green: 166 * 58
green: 188 * 98
immediate: right
immediate: right
green: 172 * 85
green: 164 * 82
immediate: right
immediate: right
yellow: 177 * 84
green: 198 * 91
immediate: right
immediate: right
green: 192 * 93
green: 182 * 89
immediate: right
immediate: right
yellow: 197 * 80
yellow: 199 * 74
immediate: right
immediate: right
green: 164 * 75
Droppable's accept-drag-type property is set to 'green'.
Droppable's accept-drag-type property is set to 'yellow'.
Click to view code
vue
<template>
<div class="dnd-demo" ref="divRef">
<div class="left-box">
<p>Draggable list:</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>
<template #drag-item v-if="item.type === 'yellow'">
<div style="width: 50px; height: 50px; background-color: yellow"></div>
</template>
</Draggable>
</div>
</div>
<div class="right-box">
<p>Droppable's accept-drag-type property is set to '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>Droppable's accept-drag-type property is set to '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' | undefinedDraggable 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> | - |
| disabled? ^1.2.0 | Whether the element is disabled | boolean | - |
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 |