因業務場景須要一個可拖拽修改節點位置的樹形組件,所以動手擼了一個,乘此機會摸了一把html5原生拖拽。近期有時間將核心部分代碼抽出,簡單說下實現方式。javascript
樹形結構很是簡單,tree組件做爲父組件,結構以下html
tree.vue前端
<template>
<div>
<Tree-Node v-for="item in data" :key="item.title" :node-data="item"></Tree-Node>
</div>
</template>
複製代碼
vue組件容許在它們本身的模板中調用自身,所以能夠造成樹形結構,在組件中必須填寫惟一的name。vue
tree-node.vuehtml5
<template>
<transition name="slide-up">
<ul :class="classes">
<li>
<div :class="[prefixCls + '-item']">
<i class="sp-icon sp-icon-arrow-right" :class="arrowClasses" @click.stop="toggleCollapseStatus()"></i>
<span :class="[prefixCls + '-title-wrap']" ref="dropTarget">
<span :class="[dragClasses,dragOverClass]" ref="draggAbleDom" v-html="nodeData.title"></span>
</span>
</div>
<Tree-Node v-for="item in nodeData.children" :key="item.title" :node-data="item" v-show="nodeData.children.length && nodeData.isExpand"></Tree-Node>
</li>
</ul>
</transition>
</template>
複製代碼
1.draggable屬性規定元素是否可拖動,目前Internet Explorer 9+, Firefox, Opera, Chrome, and Safari 支持 draggable 屬性 2.HTML 5 拖放apijava
處理拖拽節點須要幾個關鍵變量node
所以定義了一個用於保存拖拽信息的對象git
dragOverStatus: {
overNodeKey: "",
dropPosition: "",
dragNode: {}
}
複製代碼
這裏將ondragstart事件綁定在子元素上,將其餘事件綁定在父元素上,由於在測試真機IE10的時候,發現ondragstart和其餘事件綁定在同一個元素上,沒法觸發ondragenter等事件。github
<span :class="[prefixCls + '-title-wrap']" ref="dropTarget">
<span :class="[dragClasses,dragOverClass]" ref="draggAbleDom" v-html="nodeData.title"></span>
</span>
複製代碼
mounted() {
//綁定拖拽事件
if (this.root.draggable) {
this.$refs.draggAbleDom.draggable = !this.nodeData.noDrag;
this.$refs.draggAbleDom.ondragstart = this.onDragStart;
this.$refs.dropTarget.ondragenter = this.onDragEnter;
this.$refs.dropTarget.ondragover = this.onDragOver;
this.$refs.dropTarget.ondragleave = this.onDragLeave;
this.$refs.dropTarget.ondrop = this.onDrop;
this.$refs.dropTarget.ondragend = this.onDragEnd;
}
}
複製代碼
觸發某節點的拖拽事件時,就能夠從拖拽事件裏拿到當前節點實例。 使用HTML5提供的專門的拖拽與拖放API,原生的實現了複雜的操做,不須要本身用鼠標事件模擬,所以實現拖拽效果很是簡單。api
(1).開始拖拽:在拖拽元素上觸發,事件內只須要保存當前拖拽節點的信息便可
onDragStart(e, treeNode) {
this.dragOverStatus.dragNode = {
nodeData: treeNode.nodeData,
parentNode: treeNode.parentNodeData
};
this.$emit("on-dragStart", {
treeNode: treeNode.nodeData,
parentNode: treeNode.parentNodeData,
event: e
});
}
複製代碼
(2).進入目標節點:在目標元素上觸發,主要保存當前通過的節點的key,而後向外層發出事件,供組件調用者作其餘操做。爲了不拖拽一個元素快速通過許多個節點時頻繁發出事件,設置定時器當停留必定時間後觸發。
onDragEnter(e, treeNode) {
//當沒有設置拖拽節點時,禁止做爲目標節點
if (!this.hasDragNode()) {
return;
}
this.dragOverStatus.overNodeKey = "";
//拖拽節點與目標節點是同一個,return掉
if (
treeNode.nodeData._hash === this.dragOverStatus.dragNode.nodeData._hash
) {
return;
}
this.dragOverStatus.overNodeKey = treeNode.nodeData._hash; //當前通過的可放置的節點的key
//當前節點禁止作爲放置節點時
if (treeNode.nodeData.noDrop) {
return;
}
//設置dragEnter定時器,停留250毫秒後觸發事件
if (!this.delayedDragEnterLogic) {
this.delayedDragEnterLogic = {};
}
Object.keys(this.delayedDragEnterLogic).forEach(key => {
clearTimeout(this.delayedDragEnterLogic[key]);
});
this.delayedDragEnterLogic[
treeNode.nodeData._hash
] = setTimeout(() => {
if (!treeNode.nodeData.isExpand) {
treeNode.toggleCollapseStatus();
}
this.$emit("on-dragEnter", {
treeNode: treeNode.nodeData,
parentNode: treeNode.parentNodeData,
event: e
});
}, 250);
}
複製代碼
(3).在目標節點上通過:在目標元素上觸發,即時計算鼠標在目標節點上的位置,用於判斷最終的放置位置,0(做爲目標節點的子節點),-1(放置在目標節點的前面),1(放置在目標節點的後面),顯示相應的樣式。
onDragOver(e, treeNode) {
//當沒有設置拖拽節點時,禁止做爲目標節點
if (!this.hasDragNode()) {
return;
}
if (
this.dragOverStatus.overNodeKey === treeNode.nodeData._hash
) {
this.dragOverStatus.dropPosition = this.calDropPosition(e); //放置標識0,-1,1
}
this.$emit("on-dragOver", {
treeNode: treeNode.nodeData,
parentNode: treeNode.parentNodeData,
event: e
});
this.dragOverClass = this.setDragOverClass();//設置鼠標通過樣式
},
複製代碼
當鼠標處於目標節點內目標節點偏上方(1/5處),則意爲放在目標節點前面-同級,當鼠標處於目標節點內目標節點偏下方(1/5處),意爲放在目標節點後面-同級,不然做爲目標節點的子節點
calDropPosition(e) {
var offsetTop = this.getOffset(e.target).top;
var offsetHeight = e.target.offsetHeight;
var pageY = e.pageY;
var gapHeight = 0.2 * offsetHeight;
if (pageY > offsetTop + offsetHeight - gapHeight) {
//放在目標節點後面-同級
return 1;
}
if (pageY < offsetTop + gapHeight) {
//放在目標節點前面-同級
return -1;
}
//放在目標節點裏面-做爲子節點
return 0;
}
複製代碼
(4).放置節點:在目標元素上觸發,此時將拖拽的信息變量做爲參數將事件發射到外層,其他操做由外層來決定便可。
onDrop(e, treeNode) {
//當沒有設置拖拽節點時,禁止做爲目標節點
if (!this.hasDragNode()) {
return;
}
//當前節點禁止拖拽時
if (treeNode.nodeData.noDrop) {
return;
}
//拖拽節點與目標節點是同一個,不作任何操做
if (
this.dragOverStatus.dragNode.nodeData._hash === treeNode.nodeData._hash
) {
return;
}
var res = {
event: e,
dragNode: this.dragOverStatus.dragNode,
dropNode: {
nodeData: treeNode.nodeData,
parentNode: treeNode.parentNodeData
},
dropPosition: this.dragOverStatus.dropPosition
};
this.$emit("on-drop", res);
}
複製代碼
(5).拖拽結束:做用在拖拽元素上,拖拽結束後將清除變量,恢復樣式。
onDragEnd(e, treeNode) {
//當沒有設置拖拽節點時,禁止做爲目標節點
if (!this.hasDragNode()) {
return;
}
//當前節點禁止拖拽時
if (treeNode.nodeData.noDrop) {
return true;
}
this.dragOverStatus.dragNode = null;
this.dragOverStatus.overNodeKey = "";
this.$emit("on-dragEnd", {
treeNode: treeNode.nodeData,
parentNode: treeNode.parentNodeData,
event: e
});
}
複製代碼
調用樹形拖拽組件,獲取拖拽過程當中的拖拽節點,目標節點,以及放置位置,具體處理拖拽結果由調用方決定,能夠是經過調接口更新樹結構,也能夠由前端處理輸入數據,更新視圖。
<template>
<Tree :data="data1" draggable @on-drop="getDropData">
</Tree>
</template>
複製代碼
getDropData(info) {
var dragData = info.dragNode.nodeData;
var dragParent = info.dragNode.parentNode;
var dropData = info.dropNode.nodeData;
var dropParent = info.dropNode.parentNode;
var dropPosition = info.dropPosition; //0做爲子級,-1放在目標節點前面,1放在目標節點後面
//把拖拽元素從父節點中刪除
dragParent.children.splice(dragParent.children.indexOf(dragData), 1);
if (dropPosition === 0) {
dropData.children.push(dragData);
} else {
var index = dropParent.children.indexOf(dropData);
if (dropPosition === -1) {
dropParent.children.splice(index, 0, dragData);
} else {
dropParent.children.splice(index + 1, 0, dragData);
}
}
}
複製代碼
做爲子節點,改變層級
修改排序,將拖拽節點放在目標節點後面
修改排序,將拖拽節點放在目標節點前面