Skip to content

实现dragdrop指令基础用法 #101

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Dec 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/devui-vue/devui/dragdrop/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { App } from 'vue'
import DraggableDirective from './src/draggable-directive'
import DroppableDirective from './src/droppable-directive'
import SortableDirective from './src/sortable-directive'

export { DraggableDirective, DroppableDirective }
export { DraggableDirective, DroppableDirective, SortableDirective }

export default {
title: 'Dragdrop 拖拽',
Expand All @@ -11,5 +12,6 @@ export default {
install(app: App): void {
app.directive('DDraggable', DraggableDirective)
app.directive('DDroppable', DroppableDirective)
app.directive('DSortable', SortableDirective)
}
}
1 change: 1 addition & 0 deletions packages/devui-vue/devui/dragdrop/src/constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const shadowId = 'shadow0611'
40 changes: 35 additions & 5 deletions packages/devui-vue/devui/dragdrop/src/draggable-directive.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,41 @@
import { changeDragState, deleteInsertedSortableShadow } from './utils'
import { shadowId } from './constant'

export default {
mounted(el: HTMLElement): void {
el.setAttribute('draggable', 'true')
/**
*
* @param el
* @description
* 1、绑定该指令的element将会具备拖拽能力
* 2、为各元素进行初始化配置
* 2.1、dragFlag: 是否处于拖拽中
* 2.2、dragOverFlag: 是否处于可放置区域
*
* 1、整体思路
* 1.1、为每个绑定drag指令的元素维护状态
* 1.1.1、状态集合:dragStart、drag、dragover、drop、shouldCreateShadow
*
* 1.2、进入drop区域后,确保drop区域能够获取正在进行drag的元素
*/
mounted(el: HTMLElement, binding: unknown): void {
el.setAttribute('draggable', 'true');
el.style.cursor = 'grab'

// dragstart/drag/dragend
el.addEventListener('dragstart', (event: DragEvent) => {
event.dataTransfer.setData('originId', el.id)
})
el.addEventListener('drag', () => {
changeDragState(el, el.id, 'true', 'true', 'false', 'false', 'false', 'true')
if (binding.instance.$root.dropElement && document.getElementById(shadowId)){
deleteInsertedSortableShadow(binding.instance.$root.dropElement) // 如何让它仅执行1次?
binding.instance.$root.dropElement = null
}
}, false)

// dragStart事件为每个绑定元素进行初始化
el.addEventListener('dragstart', ()=>{
// el or binding.instance or vnode.context
changeDragState(el, el.id, 'true', 'true', 'false', 'false', 'false', 'false')
binding.instance.$root.identity = el.id
el.dataset.dragArea = el.parentNode.className
}, false)
},
}
48 changes: 41 additions & 7 deletions packages/devui-vue/devui/dragdrop/src/droppable-directive.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,49 @@
import { changeDragState } from './utils'

export default {
mounted(el: HTMLElement): void {
// dragenter/dragover/dragend/drop
/**
*
* @param el
* @description
* dragOver:
* 1、生成与清除阴影的时机
* 1.1、生成时机(只生成一次): dragFlag === true && dragOverFlag === true
* drop:
* 1、完成放的操作
* 1.1、清除相应的阴影
*/
mounted(el: HTMLElement, binding:unknown): void {
// dragenter/dragover/dragend/drop
el.addEventListener('dragover', (event: DragEvent) => {
event.preventDefault()
})
const dragId = binding.instance.$root.identity
changeDragState(document.getElementById(dragId), dragId, 'true', 'false', 'true', 'false', 'false', 'false')
document.getElementById(dragId).dataset.dropArea = [...el.childNodes][1].className
}, false)

// 新增两个标识解决战斗,即dragStart区域、drop区域、sortableDrop区域
el.addEventListener('drop', (event: DragEvent) => {
const originId = event.dataTransfer.getData('originId')
const originNodeCopy = document.getElementById(originId).cloneNode(true)
const targetNode: any = event.target
targetNode.append(originNodeCopy)
event.preventDefault()
const dragId = binding.instance.$root.identity
if (document.getElementById(dragId).dataset.dropArea == document.getElementById(dragId).dataset.dragArea){
return
}
// 如何定义可放置区域这个问题得商榷一下
const childrenArr = [...Array.from(el.children)[1].children]
if (childrenArr.length > 0){
for (let index = 0; index < childrenArr.length; index++){
const childrenYRange = childrenArr[index].getBoundingClientRect().top + childrenArr[index].offsetHeight / 2
if (parseFloat(event.clientY) < parseFloat(childrenYRange)){
el.children[1].insertBefore(document.getElementById(dragId), childrenArr[index])
break
}
if (index === childrenArr.length-1){
el.children[1].appendChild(document.getElementById(dragId))
}
}
}else {
el.childNodes[1].appendChild(document.getElementById(dragId))
}
})
},
}
48 changes: 48 additions & 0 deletions packages/devui-vue/devui/dragdrop/src/sortable-directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { shadowId } from './constant'
import { changeDragState, createInsertSortableShadow, insertDragElement } from './utils'


export default {
/**
*
* @param el
* @description
* 此命令用于将元素变为可放置的元素并且支持排序
* dragover:
* 1、说明此时进入可排序放置的区域
* 2、此时应该生成相应的可排序的shadow
* drop:
* 1、可放置区域里如果没有拖拽元素,直接放置
* 2、可放置区域里如果有其他的可拖拽元素,需要对比放置到正确的位置上
*/
mounted(el: HTMLElement, binding:unknown):void {
el.addEventListener('dragover', function (event: DragEvent){
event.preventDefault()
const targetNode: any = event.target;
const dragId = binding.instance.$root.identity
if (!binding.instance.$root.dropElement){
binding.instance.$root.dropElement = [...el.childNodes][1]
}
changeDragState(document.getElementById(binding.instance.$root.identity), binding.instance.$root.identity, 'true', 'false', 'true', 'false', 'true', 'false')
const { dragover, shouldCreateShadow } = document.getElementById(dragId).dataset
if (dragover == 'true'){
if (shouldCreateShadow == 'true'){
createInsertSortableShadow([...targetNode.children][1], event, dragId)
}
}

})
el.addEventListener('drop', function (event: DragEvent){
// 获取可放置区域
const dropArea = [...el.childNodes][1]
const dragId = binding.instance.$root.identity
dropArea.removeChild(document.getElementById(shadowId))
if ([...dropArea.childNodes].length == 0){
dropArea.appendChild(document.getElementById(dragId))
}else {
insertDragElement(dropArea, dragId, event)
}
changeDragState(document.getElementById(dragId), dragId, 'false', 'false', 'false', 'true', 'false', 'false')
})
}
}
143 changes: 143 additions & 0 deletions packages/devui-vue/devui/dragdrop/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { shadowId } from './constant'

/**
*
* @param id
* @descriprion
* 根据id获取非内联样式元素的样式
*/
function getElementStyle (id: string, styleName: string):string {
return document.getElementById(id).currentStyle ? document.getElementById(id).currentStyle[styleName] : window.getComputedStyle(
document.getElementById(id),
styleName
)
}

/**
*
* @param originId
* @description
* 根据拖拽的id生成相应的阴影
* 如何生成shadow?
* 情况一: dragable -> drop without sortable
* 情况二: anything -> drop without anything
*/
function createShadow (originId:string):HTMLElement {
const shadow = document.createElement('div');
shadow.id = shadowId
shadow.style.background = 'rgb(206, 215, 255)'
shadow.style.width = getElementStyle(originId, 'width')
shadow.style.height = '20px'
return shadow
}

/**
*
* @param el
* @param originId
* @param dragStart
* @param drag
* @param dragover
* @param drop
* @param shouldCreateShadow
* @param dragFlag
* @description
* 改变拖拽元素相应的状态
*/
function changeDragState (el:string, originId:string, dragStart:string, drag:string, dragover:string, drop:string, shouldCreateShadow:string, dragFlag: string): void{
el.dataset.originId = originId
el.dataset.dragStart = dragStart
el.dataset.dragover = dragover
el.dataset.drop = drop
el.dataset.shouldCreateShadow = shouldCreateShadow
el.dataset.dragFlag = dragFlag
}

/**
*
* @param compareElement
* @returns
* @description
* 计算可对比元素的高度
*/
function computeCompareElementHeight (compareElement: HTMLCollection): unknown{
return compareElement.getBoundingClientRect().top + Math.floor(compareElement.offsetHeight / 2)
}

/**
*
* @param sortDropArea
* @param mouseObject
* 1、首先确认可放置区域
* 2、确保每个元素只生成一次shadow
* 3、
*/
function createInsertSortableShadow (sortDropArea: unknown, mouseObject: unknown, originId: string):void {
const sortDropAreaArr: Array = [...sortDropArea.children]
if (sortDropAreaArr.length == 0){
if (!document.getElementById(shadowId)){
const shadowElement = createShadow(originId)
sortDropArea.appendChild(shadowElement)
}
}else {
for (let index = 0; index < sortDropAreaArr.length; index++){
const compareHeight = computeCompareElementHeight(sortDropAreaArr[index])
document.getElementById(shadowId) ? sortDropArea.removeChild(document.getElementById(shadowId)) : null
if (index == sortDropAreaArr.length-1){
sortDropArea.appendChild(createShadow(originId))
break
}
if (Math.floor(mouseObject.clientY)<= compareHeight){
sortDropArea.insertBefore(createShadow(originId), sortDropAreaArr[index])
break
}
}
}
}

/**
*
* @param dropAreaContainer
* @param dragId
* @param mouseObject
* @description
* 向sortable区域插入拖拽元素
*/
function insertDragElement (dropAreaContainer: HTMLCollection, dragId: string, mouseObject: MouseEvent): void {
for (let index = 0; index < [...dropAreaContainer.children].length; index++){
if (index == [...dropAreaContainer.children].length-1){
dropAreaContainer.appendChild(document.getElementById(dragId))
break
}
if (Math.floor(mouseObject.clientY) <= computeCompareElementHeight([...dropAreaContainer.children][index])){
dropAreaContainer.insertBefore(document.getElementById(dragId), [...dropAreaContainer.children][index])
break
}
}
}

/**
*
* @param dropSortArea
* @description
* 删除可排序区域中的shadow
*/
function deleteInsertedSortableShadow (dropSortArea: unknown):void{
if (dropSortArea){
if (document.getElementById(shadowId)){
if (dropSortArea.contains(document.getElementById(shadowId))){
dropSortArea.removeChild(document.getElementById(shadowId))
}
}
}
}


export {
createShadow,
changeDragState,
createInsertSortableShadow,
deleteInsertedSortableShadow,
computeCompareElementHeight,
insertDragElement
}
12 changes: 10 additions & 2 deletions packages/devui-vue/docs/components/dragdrop/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,27 @@
<div class="dragdrop-card-container">
<div class="dragdrop-card">
<div class="dragdrop-card-header">Draggable Item</div>
<div class="dragdrop-card-block">
<div class="dragdrop-card-block drag">
<div id="draggable-item" class="draggable-item" v-d-draggable="{
dragScope: 'default-css',
dragData: { item: 'item', parent: 'list1' },
}">VSCode</div>
<div id="draggable-item2" class="draggable-item" v-d-draggable="{
dragScope: 'default-css',
dragData: { item: 'item', parent: 'list1' },
}">Sublime</div>
</div>
</div>
<div class="dragdrop-card" v-d-droppable>
<div class="dragdrop-card-header">Drop Area</div>
<div class="dragdrop-card-block">
<div class="dragdrop-card-block droppable">

</div>
</div>
<div class="dragdrop-card" v-d-sortable>
<div class="dragdrop-card-header">Drop Area With Sortable</div>
<div class="dragdrop-card-block"></div>
</div>
</div>
</template>

Expand Down