Skip to content

Commit c2dd1f6

Browse files
authored
实现dragdrop指令基础用法 (#101)
* docs: 增加tooltip英文文档 * fixed: v-d-draggable、droppable基本用法完成 * feat: draggable、droppable最基础指令完成 * 拖拽指令基础用法已完成 * feat: 基础用法已完成 * feat: 新增dragable指令不可排序功能
1 parent 540a8a5 commit c2dd1f6

File tree

7 files changed

+281
-15
lines changed

7 files changed

+281
-15
lines changed
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import type { App } from 'vue'
22
import DraggableDirective from './src/draggable-directive'
33
import DroppableDirective from './src/droppable-directive'
4+
import SortableDirective from './src/sortable-directive'
45

5-
export { DraggableDirective, DroppableDirective }
6+
export { DraggableDirective, DroppableDirective, SortableDirective }
67

78
export default {
89
title: 'Dragdrop 拖拽',
@@ -11,5 +12,6 @@ export default {
1112
install(app: App): void {
1213
app.directive('DDraggable', DraggableDirective)
1314
app.directive('DDroppable', DroppableDirective)
15+
app.directive('DSortable', SortableDirective)
1416
}
1517
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const shadowId = 'shadow0611'
Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,41 @@
1+
import { changeDragState, deleteInsertedSortableShadow } from './utils'
2+
import { shadowId } from './constant'
3+
14
export default {
2-
mounted(el: HTMLElement): void {
3-
el.setAttribute('draggable', 'true')
5+
/**
6+
*
7+
* @param el
8+
* @description
9+
* 1、绑定该指令的element将会具备拖拽能力
10+
* 2、为各元素进行初始化配置
11+
* 2.1、dragFlag: 是否处于拖拽中
12+
* 2.2、dragOverFlag: 是否处于可放置区域
13+
*
14+
* 1、整体思路
15+
* 1.1、为每个绑定drag指令的元素维护状态
16+
* 1.1.1、状态集合:dragStart、drag、dragover、drop、shouldCreateShadow
17+
*
18+
* 1.2、进入drop区域后,确保drop区域能够获取正在进行drag的元素
19+
*/
20+
mounted(el: HTMLElement, binding: unknown): void {
21+
el.setAttribute('draggable', 'true');
422
el.style.cursor = 'grab'
523

624
// dragstart/drag/dragend
7-
el.addEventListener('dragstart', (event: DragEvent) => {
8-
event.dataTransfer.setData('originId', el.id)
9-
})
25+
el.addEventListener('drag', () => {
26+
changeDragState(el, el.id, 'true', 'true', 'false', 'false', 'false', 'true')
27+
if (binding.instance.$root.dropElement && document.getElementById(shadowId)){
28+
deleteInsertedSortableShadow(binding.instance.$root.dropElement) // 如何让它仅执行1次?
29+
binding.instance.$root.dropElement = null
30+
}
31+
}, false)
32+
33+
// dragStart事件为每个绑定元素进行初始化
34+
el.addEventListener('dragstart', ()=>{
35+
// el or binding.instance or vnode.context
36+
changeDragState(el, el.id, 'true', 'true', 'false', 'false', 'false', 'false')
37+
binding.instance.$root.identity = el.id
38+
el.dataset.dragArea = el.parentNode.className
39+
}, false)
1040
},
1141
}
Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,49 @@
1+
import { changeDragState } from './utils'
2+
13
export default {
2-
mounted(el: HTMLElement): void {
3-
// dragenter/dragover/dragend/drop
4+
/**
5+
*
6+
* @param el
7+
* @description
8+
* dragOver:
9+
* 1、生成与清除阴影的时机
10+
* 1.1、生成时机(只生成一次): dragFlag === true && dragOverFlag === true
11+
* drop:
12+
* 1、完成放的操作
13+
* 1.1、清除相应的阴影
14+
*/
15+
mounted(el: HTMLElement, binding:unknown): void {
16+
// dragenter/dragover/dragend/drop
417
el.addEventListener('dragover', (event: DragEvent) => {
518
event.preventDefault()
6-
})
19+
const dragId = binding.instance.$root.identity
20+
changeDragState(document.getElementById(dragId), dragId, 'true', 'false', 'true', 'false', 'false', 'false')
21+
document.getElementById(dragId).dataset.dropArea = [...el.childNodes][1].className
22+
}, false)
723

24+
// 新增两个标识解决战斗,即dragStart区域、drop区域、sortableDrop区域
825
el.addEventListener('drop', (event: DragEvent) => {
9-
const originId = event.dataTransfer.getData('originId')
10-
const originNodeCopy = document.getElementById(originId).cloneNode(true)
11-
const targetNode: any = event.target
12-
targetNode.append(originNodeCopy)
26+
event.preventDefault()
27+
const dragId = binding.instance.$root.identity
28+
if (document.getElementById(dragId).dataset.dropArea == document.getElementById(dragId).dataset.dragArea){
29+
return
30+
}
31+
// 如何定义可放置区域这个问题得商榷一下
32+
const childrenArr = [...Array.from(el.children)[1].children]
33+
if (childrenArr.length > 0){
34+
for (let index = 0; index < childrenArr.length; index++){
35+
const childrenYRange = childrenArr[index].getBoundingClientRect().top + childrenArr[index].offsetHeight / 2
36+
if (parseFloat(event.clientY) < parseFloat(childrenYRange)){
37+
el.children[1].insertBefore(document.getElementById(dragId), childrenArr[index])
38+
break
39+
}
40+
if (index === childrenArr.length-1){
41+
el.children[1].appendChild(document.getElementById(dragId))
42+
}
43+
}
44+
}else {
45+
el.childNodes[1].appendChild(document.getElementById(dragId))
46+
}
1347
})
1448
},
1549
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { shadowId } from './constant'
2+
import { changeDragState, createInsertSortableShadow, insertDragElement } from './utils'
3+
4+
5+
export default {
6+
/**
7+
*
8+
* @param el
9+
* @description
10+
* 此命令用于将元素变为可放置的元素并且支持排序
11+
* dragover:
12+
* 1、说明此时进入可排序放置的区域
13+
* 2、此时应该生成相应的可排序的shadow
14+
* drop:
15+
* 1、可放置区域里如果没有拖拽元素,直接放置
16+
* 2、可放置区域里如果有其他的可拖拽元素,需要对比放置到正确的位置上
17+
*/
18+
mounted(el: HTMLElement, binding:unknown):void {
19+
el.addEventListener('dragover', function (event: DragEvent){
20+
event.preventDefault()
21+
const targetNode: any = event.target;
22+
const dragId = binding.instance.$root.identity
23+
if (!binding.instance.$root.dropElement){
24+
binding.instance.$root.dropElement = [...el.childNodes][1]
25+
}
26+
changeDragState(document.getElementById(binding.instance.$root.identity), binding.instance.$root.identity, 'true', 'false', 'true', 'false', 'true', 'false')
27+
const { dragover, shouldCreateShadow } = document.getElementById(dragId).dataset
28+
if (dragover == 'true'){
29+
if (shouldCreateShadow == 'true'){
30+
createInsertSortableShadow([...targetNode.children][1], event, dragId)
31+
}
32+
}
33+
34+
})
35+
el.addEventListener('drop', function (event: DragEvent){
36+
// 获取可放置区域
37+
const dropArea = [...el.childNodes][1]
38+
const dragId = binding.instance.$root.identity
39+
dropArea.removeChild(document.getElementById(shadowId))
40+
if ([...dropArea.childNodes].length == 0){
41+
dropArea.appendChild(document.getElementById(dragId))
42+
}else {
43+
insertDragElement(dropArea, dragId, event)
44+
}
45+
changeDragState(document.getElementById(dragId), dragId, 'false', 'false', 'false', 'true', 'false', 'false')
46+
})
47+
}
48+
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { shadowId } from './constant'
2+
3+
/**
4+
*
5+
* @param id
6+
* @descriprion
7+
* 根据id获取非内联样式元素的样式
8+
*/
9+
function getElementStyle (id: string, styleName: string):string {
10+
return document.getElementById(id).currentStyle ? document.getElementById(id).currentStyle[styleName] : window.getComputedStyle(
11+
document.getElementById(id),
12+
styleName
13+
)
14+
}
15+
16+
/**
17+
*
18+
* @param originId
19+
* @description
20+
* 根据拖拽的id生成相应的阴影
21+
* 如何生成shadow?
22+
* 情况一: dragable -> drop without sortable
23+
* 情况二: anything -> drop without anything
24+
*/
25+
function createShadow (originId:string):HTMLElement {
26+
const shadow = document.createElement('div');
27+
shadow.id = shadowId
28+
shadow.style.background = 'rgb(206, 215, 255)'
29+
shadow.style.width = getElementStyle(originId, 'width')
30+
shadow.style.height = '20px'
31+
return shadow
32+
}
33+
34+
/**
35+
*
36+
* @param el
37+
* @param originId
38+
* @param dragStart
39+
* @param drag
40+
* @param dragover
41+
* @param drop
42+
* @param shouldCreateShadow
43+
* @param dragFlag
44+
* @description
45+
* 改变拖拽元素相应的状态
46+
*/
47+
function changeDragState (el:string, originId:string, dragStart:string, drag:string, dragover:string, drop:string, shouldCreateShadow:string, dragFlag: string): void{
48+
el.dataset.originId = originId
49+
el.dataset.dragStart = dragStart
50+
el.dataset.dragover = dragover
51+
el.dataset.drop = drop
52+
el.dataset.shouldCreateShadow = shouldCreateShadow
53+
el.dataset.dragFlag = dragFlag
54+
}
55+
56+
/**
57+
*
58+
* @param compareElement
59+
* @returns
60+
* @description
61+
* 计算可对比元素的高度
62+
*/
63+
function computeCompareElementHeight (compareElement: HTMLCollection): unknown{
64+
return compareElement.getBoundingClientRect().top + Math.floor(compareElement.offsetHeight / 2)
65+
}
66+
67+
/**
68+
*
69+
* @param sortDropArea
70+
* @param mouseObject
71+
* 1、首先确认可放置区域
72+
* 2、确保每个元素只生成一次shadow
73+
* 3、
74+
*/
75+
function createInsertSortableShadow (sortDropArea: unknown, mouseObject: unknown, originId: string):void {
76+
const sortDropAreaArr: Array = [...sortDropArea.children]
77+
if (sortDropAreaArr.length == 0){
78+
if (!document.getElementById(shadowId)){
79+
const shadowElement = createShadow(originId)
80+
sortDropArea.appendChild(shadowElement)
81+
}
82+
}else {
83+
for (let index = 0; index < sortDropAreaArr.length; index++){
84+
const compareHeight = computeCompareElementHeight(sortDropAreaArr[index])
85+
document.getElementById(shadowId) ? sortDropArea.removeChild(document.getElementById(shadowId)) : null
86+
if (index == sortDropAreaArr.length-1){
87+
sortDropArea.appendChild(createShadow(originId))
88+
break
89+
}
90+
if (Math.floor(mouseObject.clientY)<= compareHeight){
91+
sortDropArea.insertBefore(createShadow(originId), sortDropAreaArr[index])
92+
break
93+
}
94+
}
95+
}
96+
}
97+
98+
/**
99+
*
100+
* @param dropAreaContainer
101+
* @param dragId
102+
* @param mouseObject
103+
* @description
104+
* 向sortable区域插入拖拽元素
105+
*/
106+
function insertDragElement (dropAreaContainer: HTMLCollection, dragId: string, mouseObject: MouseEvent): void {
107+
for (let index = 0; index < [...dropAreaContainer.children].length; index++){
108+
if (index == [...dropAreaContainer.children].length-1){
109+
dropAreaContainer.appendChild(document.getElementById(dragId))
110+
break
111+
}
112+
if (Math.floor(mouseObject.clientY) <= computeCompareElementHeight([...dropAreaContainer.children][index])){
113+
dropAreaContainer.insertBefore(document.getElementById(dragId), [...dropAreaContainer.children][index])
114+
break
115+
}
116+
}
117+
}
118+
119+
/**
120+
*
121+
* @param dropSortArea
122+
* @description
123+
* 删除可排序区域中的shadow
124+
*/
125+
function deleteInsertedSortableShadow (dropSortArea: unknown):void{
126+
if (dropSortArea){
127+
if (document.getElementById(shadowId)){
128+
if (dropSortArea.contains(document.getElementById(shadowId))){
129+
dropSortArea.removeChild(document.getElementById(shadowId))
130+
}
131+
}
132+
}
133+
}
134+
135+
136+
export {
137+
createShadow,
138+
changeDragState,
139+
createInsertSortableShadow,
140+
deleteInsertedSortableShadow,
141+
computeCompareElementHeight,
142+
insertDragElement
143+
}

packages/devui-vue/docs/components/dragdrop/index.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,27 @@
1515
<div class="dragdrop-card-container">
1616
<div class="dragdrop-card">
1717
<div class="dragdrop-card-header">Draggable Item</div>
18-
<div class="dragdrop-card-block">
18+
<div class="dragdrop-card-block drag">
1919
<div id="draggable-item" class="draggable-item" v-d-draggable="{
2020
dragScope: 'default-css',
2121
dragData: { item: 'item', parent: 'list1' },
2222
}">VSCode</div>
23+
<div id="draggable-item2" class="draggable-item" v-d-draggable="{
24+
dragScope: 'default-css',
25+
dragData: { item: 'item', parent: 'list1' },
26+
}">Sublime</div>
2327
</div>
2428
</div>
2529
<div class="dragdrop-card" v-d-droppable>
2630
<div class="dragdrop-card-header">Drop Area</div>
27-
<div class="dragdrop-card-block">
31+
<div class="dragdrop-card-block droppable">
2832
2933
</div>
3034
</div>
35+
<div class="dragdrop-card" v-d-sortable>
36+
<div class="dragdrop-card-header">Drop Area With Sortable</div>
37+
<div class="dragdrop-card-block"></div>
38+
</div>
3139
</div>
3240
</template>
3341

0 commit comments

Comments
 (0)