好傢伙,
本篇我們來說說,編輯器內如何實現拖拽
完整程式碼已開源
https://github.com/Fattiger4399/ph-questionnaire.git
0.效果預覽
1.思路
1.1.檢視操作分析
這一塊是這一章節最核心的部分
到
使用者進行了什麼操作?
(1)點選編輯器中第一個元件
(2)鬆開
(3)在setter中修改第一個元件的資料
(4)按下第一個元件(不鬆開滑鼠左鍵)
(5)拖拽(不鬆開滑鼠左鍵)
(6)到達目標地點(鬆開滑鼠左鍵)
上述操作中,只有第六個是需要我們進行分析的
但其實也非常簡單,
圖片中畫紅框的區域如何確定呢?
目標區域的位置 小於 第四個元件的開始座標 加上 一半的第四個元件的高度
並且
目標區域的位置 大於 第四個元件的開始座標 減去 一半的第三個元件的高度
換成公式大概長這個樣子
mouseupY > phoffsetTopbox[i] + 0.5 * phoffsetHeightbox[i] && mouseupY < phoffsetTopbox[i + 1] + 0.5 * phoffsetHeightbox[i + 1]
放到四個後面。。。放到第五個後面。。。
後面的情況以此類推
好了
1.2.資料操作分析
編輯器本身就是按資料的順序渲染的,所以,只要排好序,然後更新渲染器就可以了
2.開始操作
我們需要用到的一些座標
事件物件e的一些屬性
isTrusted
: 表示事件是否是由使用者操作觸發的,如果是由指令碼建立的事件,則為false
,如果是由使用者操作觸發的則為true
。altKey
,ctrlKey
,metaKey
,shiftKey
: 分別表示是否按下了 Alt、Ctrl、Meta、Shift 鍵。bubbles
: 表示該事件是否會冒泡。button
: 表示按下的是哪個滑鼠按鈕(左鍵為 0,中鍵為 1,右鍵為 2)。clientX
,clientY
: 表示滑鼠指標在視口中的座標。pageX
,pageY
: 表示滑鼠指標相對於頁面的座標。screenX
,screenY
: 表示滑鼠指標相對於螢幕的座標。target
: 表示事件的目標元素。type
: 表示事件的型別,這裡是 "dragend"。timeStamp
: 表示事件發生的時間戳。x
,y
: 與clientX
,clientY
相同,表示滑鼠指標在視口中的座標。offsetX
: 表示滑鼠指標位置相對於觸發事件的物件的 X 座標。換句話說,它是滑鼠指標距離事件目標元素的左側邊緣的畫素距離。offsetY
: 表示滑鼠指標位置相對於觸發事件的物件的 Y 座標。它是滑鼠指標距離事件目標元素的頂部邊緣的畫素距離。
3.程式碼分析
(以下僅分析關鍵程式碼,完整程式碼請參考)
select(config) {
// 去除所有選中樣式
this.dsl = removeChildrenBorder(this.dsl);
// 新增選中樣式
config.dsl = addChildrenBorder(config.dsl);
this.model.selected = config.dsl;
// 元件拖拽處理
const editorElement = this.$refs.editor.$el;
const node = `div.${this.model.selected.component}`;
const allElements = editorElement.querySelectorAll('div');
const childElements = editorElement.querySelectorAll(node);
let sameid = this.model.selected.wid - 1;
// 篩選出以 ph- 開頭的 div 元素
const phElements = Array.from(allElements).filter(div => {
// 獲取元素的類名陣列
const classList = div.classList;
// 檢查是否有任何類名以 'ph-' 開頭
return Array.from(classList).some(className => className.startsWith('ph-'));
});
let phoffsetTopbox = []
let phoffsetHeightbox = []
// 列印出所有匹配的元素
phElements.forEach((item) => {
phoffsetTopbox.push(item.offsetTop)
phoffsetHeightbox.push(item.offsetHeight)
})
// 定義事件處理函式,並儲存引用
this.dragStartHandler = (e) => {
};
this.dragEndHandler = (e) => {
// 注意:這裡不需要移除事件監聽器,因為它們會在select方法開始時被移除
// console.log(e);
// console.log("mouseupY軸 " + e.offsetY);
//進行位置交換操作
let newdsl;
if (e.offsetY) {
const arraylength = this.dsl.children.length;
console.log(arraylength, phoffsetTopbox)
//將一號位元件拖拽到三號位上方
//拖拽大約為範圍為150-250px
let i, j;
let overdragid;
let using_id = this.model.selected.wid;
//phoffsetTopbox
//phoffsetHeightbox
const mouseupY = phoffsetTopbox[using_id - 1] + e.offsetY;
const childlength = this.dsl.children.length
console.log(mouseupY)
//向下
if (e.offsetY > 0) {
for (i = using_id; i <= this.dsl.children.length;) {
// console.log(i)
// console.log(phoffsetTopbox[i], phoffsetHeightbox[i])
//向下
if (mouseupY > phoffsetTopbox[i] + 0.5 * phoffsetHeightbox[i] && mouseupY < phoffsetTopbox[i + 1] + 0.5 * phoffsetHeightbox[i + 1]) {
i++;
break;
} else if (mouseupY > 0 && mouseupY < phoffsetTopbox[i] + 0.5 * phoffsetHeightbox[i]) {
break;
} else if (mouseupY > phoffsetTopbox[childlength - 1] + 0.5 * phoffsetHeightbox[childlength - 1]) {
i = childlength;
break;
}
else {
i++;
}
}
} else {
//向上
for (i = using_id; i > 0;) {
// console.log(i)
console.log(mouseupY)
if (mouseupY > phoffsetTopbox[i - 2] - 0.5 * phoffsetHeightbox[i - 2] && mouseupY < phoffsetTopbox[i - 1] - 0.5 * phoffsetHeightbox[i - 1]) {
i = i - 2;
break;
}
//上半部分
else if (mouseupY < phoffsetTopbox[i] && mouseupY > phoffsetTopbox[i - 2] + 0.5 * phoffsetHeightbox[i - 2]) {
break;
} else if (mouseupY < phoffsetTopbox[0] + 0.5 * phoffsetHeightbox[0]) {
i = 0;
break;
} else {
i--;
}
}
}
console.log(this.dsl.children)
if (this.model.selected.wid != i) {
//複製
let copied_element = this.dsl.children[this.model.selected.wid - 1]
// //插入
this.dsl.children.splice(i, 0, copied_element)
//刪除
this.dsl.children.splice(using_id - 1, 1)
}
console.log(i)
}
};
// 在新增新的監聽器之前,先移除舊的監聽器
if (childElements[sameid]) {
childElements[sameid].removeEventListener('dragstart', this.dragStartHandler);
childElements[sameid].removeEventListener('dragend', this.dragEndHandler);
}
// 新增新的監聽器
childElements[sameid].setAttribute("draggable", "true");
childElements[sameid].addEventListener('dragstart', this.dragStartHandler);
childElements[sameid].addEventListener('dragend', this.dragEndHandler);
//更新檢視
this.$refs.editor.$forceUpdate();
},
-
首先,根據傳入的
config
物件,更新選中的樣式,將選中的元件標記為config.dsl
。 -
然後,透過獲取編輯器元素和選中元件的類名,找到所有符合條件的子元素並儲存在
childElements
中。 -
根據選中元件的
wid
屬性計算出sameid
,用於定位對應的子元素。 -
透過篩選出以
ph-
開頭的 div 元素,計算出這些元素的offsetTop
和offsetHeight
,並儲存在phoffsetTopbox
和phoffsetHeightbox
陣列中。 -
定義了兩個事件處理函式
dragStartHandler
和dragEndHandler
,分別處理拖拽開始和拖拽結束的邏輯。 -
在拖拽結束時,根據滑鼠在 Y 軸上的位置,判斷拖拽的方向(向上或向下),並根據計算得到的位置資訊,將選中的元件插入到新的位置上。
-
移除舊的事件監聽器,新增新的事件監聽器,並設定元素為可拖拽狀態。