先說實現的邏輯(針對操作邏輯對應的事件講解):
onmousedown(滑鼠按下事件):
重新獲取操作盒子的大小,以及座標(考慮到客戶有可能變更瀏覽器大小的情況)。
- 獲取操作盒子頂部距離
- 獲取操作盒子左邊距離
- 獲取操作盒子的高度
- 獲取操作盒子的寬度
滑鼠按下獲取到按下的item資料,所在item的座標,(讓隨滑鼠移動的臨時dom所在滑鼠的位置完全對應上按下時的位置,使用者體驗像真複製出來的一樣)
- 獲取按下時陣列的index,要刪除的位置
- 滑鼠點選時的頂部距離 = 滑鼠距離瀏覽器的頂部的高度
event.clientY
+ 瀏覽器滾動條隱藏的高度document.documentElement.scrollTop
- 滑鼠點選時的左邊距離 = 滑鼠距離瀏覽器的左邊的高度
event.clientX
+ 瀏覽器滾動條隱藏的距離document.documentElement.scrollLeft
- item的左邊距離 = 滑鼠點選時的左邊距離 - 獲取操作盒子左邊距離
- item的頂部距離 = (滑鼠點選時的頂部距離 - 獲取操作盒子頂部距離)% (每個item的高度+邊距(我固定了位置這裡設定為88,不固定的話可另外計算))
賦值給一個隨滑鼠移動的臨時dom上,先不顯示(因為點選是可開啟編輯的,移動時再顯示)
onmousermove(滑鼠移動時的事件):
- 判斷臨時的dom是否有資料,有資料的時候進行移動,顯示出來
- 設定臨時dom的top = 滑鼠頂部位置event.clientY - 按下時獲取到的item的頂部距離 + ‘px’(因為顯示的就是距離瀏覽器視窗座標,所以不用加上滾動條隱藏的位置)
- 設定臨時dom的left = 滑鼠左邊位置event.clientX - 按下時獲取到的item的左邊距離 + ‘px’
- 獲取滑鼠移動到的index位置 = ((滑鼠獲取到的頂部距離+滾動條隱藏的距離) - 獲取操作盒子頂部距離)/ (每個item的高度+邊距(我固定了位置這裡設定為88,不固定的話可另外計算))四捨五入取整
onmouserup(滑鼠釋放時的事件):
我們獲取到了要刪除的index,獲取到了滑鼠釋放前停留的index。只要做陣列插入,再重置獲取到的資料就可以了。可以做一些細小的調整,比如滑鼠移動到的位置進行高亮顯示,要插入資料的準確性等等
然後,普及一下需要用到的座標獲取方法
獲取操作盒子的寬度offsetWidth
及高度offsetHeight
給盒子一個ref,不是vue就操作dom
this.offset_width = this.$refs.componentBox.offsetWidth
this.offset_height = this.$refs.componentBox.offsetHeight
複製程式碼
獲取操作盒子左上角的座標
offsetLeft
和offsetTop
這兩個方法是距離父元素文件左邊距離,距離父元素文件頂部距離,可通過offsetParent
做遍歷相加,拿到準確座標
// 獲取盒子左上角座標
const componentBox = this.$refs.componentBox
let top = componentBox.offsetTop
let left = componentBox.offsetLeft
let parent = componentBox.offsetParent
//每一層父元素相加一次距離
while (parent !== null){
left += parent.offsetLeft;
top += parent.offsetTop;
parent = parent.offsetParent;
   }
複製程式碼
獲取滑鼠距離瀏覽器頂部座標event.clientY,但是瀏覽器可能有滾動條下拉的情況,所以要得到頁面的上的準確位置 = 滑鼠座標 + 瀏覽器被隱藏距(document.documentElement.scrollTop)
最後貼上部分主要實現程式碼
<template>
<div ref="componentBox" @mousemove="onmousermoves">
<div>
<div
v-for="(component,componentIndex) in currentCustomPage.components"
:key="componentIndex"
:class="insertIndex === componentIndex?'move-active':''||(insertIndex === -1&&componentIndex===0)?'move-active-top':''"
class="component-item"
@mousedown="onmousedowns($event,componentIndex)"
>
<div class="name" @click="handleEditComponent(componentIndex)">{{ component.type_data.data.name }}</div>
<div class="component-item_delete" @click="handleDeleteComponent(componentIndex)">刪除</div>
</div>
</div>
<!-- 滑鼠按下臨時dom -->
<div v-show="mousedownInfo.status" @mouseup="onmouseups" ref="moveItem" class="mousedownInfo-item">
<div class="name" >{{ mousedownInfo.name }}</div>
</div>
</div>
</template>
<style scoped>
.move-active{
border-bottom:2px #409EFF solid!important;
box-shadow:0 1px 2px #409EFF!important;;
}
.move-active-top{
border-top:2px #409EFF solid!important;
box-shadow:1px 0 2px #409EFF!important;;
}
</style>
<script>
export default{
data(){
return {
// 拖拽
offset_top: null,
offset_left: null,
offset_width: null,
offset_height: null,
// 臨時儲存資訊,隨滑鼠移動
mousedownInfo: {
name: '',
index: null,
status:false,
width:0,
height:0
},
// 要插入的index
insertIndex:null,
}
},
methods:{
// 獲取移動父盒子的座標,大小
handMove() {
const componentBox = this.$refs.componentBox
let top = componentBox.offsetTop
let left = componentBox.offsetLeft
let parent = componentBox.offsetParent
// 獲取盒子左上角座標
//每一層父元素相加一次距離
while (parent !== null){
left += parent.offsetLeft;
top += parent.offsetTop;
parent = parent.offsetParent;
    }
this.offset_top = top
this.offset_left = left
this.offset_width = componentBox.offsetWidth
this.offset_height = componentBox.offsetHeight
// 初始化移動item的寬度
this.$refs.moveItem.style.width = componentBox.offsetWidth + 'px'
},
// 滑鼠按下臨時儲存
onmousedowns(event, index) {
// 獲取移動父盒子的座標,大小
this.handMove()
const e= event || window.event
this.mousedownInfo.name = this.currentCustomPage.components[index].type_data.data.name
// 儲存要刪除的index
this.mousedownInfo.index = index
// 滑鼠距離點選元素的寬高
this.mousedownInfo.width = (e.clientX+document.documentElement.scrollLeft) - this.offset_left
// (滑鼠高度+超出瀏覽器高度) - 父盒子高度 / 每個item的高度88,取模% = 滑鼠所在item的高度
this.mousedownInfo.height = ((e.clientY+document.documentElement.scrollTop) - this.offset_top) % 88
// 按下先初始化位置
/* this.$refs.moveItem.style.top = (e.clientY - this.mousedownInfo.height) + 'px'
this.$refs.moveItem.style.left = (e.clientX - this.mousedownInfo.width) + 'px' */
},
// 滑鼠移動時
onmousermoves(event) {
if(this.mousedownInfo.name){
const e = event || window.event
// 判斷滑鼠是否移出了盒子
if( (e.clientX-this.offset_left)<0 ||(e.clientX-this.offset_left)>this.offset_width
|| ((e.clientY+document.documentElement.scrollTop)- this.offset_top)<-88
|| ((e.clientY+document.documentElement.scrollTop)- this.offset_top)>Number(this.offset_height)+88
)
{
this.resetMove()
return false
}
// 按下移動時展示,不拖拽可以進入編輯
this.$set(this.mousedownInfo,'status',true)
const stateIndex = ((e.clientY+document.documentElement.scrollTop)- this.offset_top) / 88
this.insertIndex = (stateIndex.toFixed(0))-1
// 根據滑鼠位置計算移動item的位置
this.$refs.moveItem.style.top = (e.clientY - this.mousedownInfo.height) + 'px'
this.$refs.moveItem.style.left = (e.clientX - this.mousedownInfo.width) + 'px'
}
},
// 滑鼠釋放時,
// 進行元素的位置更改
onmouseups() {
const item = this.currentCustomPage.components.splice(this.mousedownInfo.index,1)
this.currentCustomPage.components.splice(this.insertIndex+1,0,item[0])
this.resetMove()
},
// 重置拖拽資料
resetMove(){
this.mousedownInfo={
name:null,
index:null,
width:0,
height:0,
}
this.insertIndex = null
this.mousedownInfo.status = false;
},
}
}
</script>
複製程式碼