折騰兩天也做出一個(gè)codepen的大概模型,目前只兼容了chrome。前端代碼用的react作為ui庫
分析codepen
首先要分析codepen的編輯器界面

codepen編輯器樣式
- 紅色圈起來的地方是由codemirror完成的,只要看源碼就知道了
- 藍(lán)色標(biāo)注的兩個(gè)拖拽bar,可以修改窗口大小
這兩條里 codemirror實(shí)現(xiàn)的只要引入就行了,主要難點(diǎn)就在如何做出一個(gè)可以任意調(diào)整分塊大小的編輯器容器。
設(shè)計(jì)模型
由codepen設(shè)計(jì)可以看出,每個(gè)拖拽的bar可以影響兩邊的窗口,當(dāng)其中一邊窗口大小不夠的時(shí)候,會影響那一邊相鄰的窗口。
假設(shè)有3個(gè)窗口 A,B,C 拖拽bar有 AB和BC。
- 拖拽AB向右側(cè)移動的時(shí)候,A窗口會增加寬度,B窗口減小寬度,當(dāng)B窗口為0的時(shí)候,C窗口會相應(yīng)減少寬度,返回過來BC向左也是一樣的。
推廣到n個(gè)窗口的時(shí)候,情況是類似的。
- 這里將拖拽的距離設(shè)置為消費(fèi)的能量cost
- 拖拽bar其中一側(cè)會增加cost的寬度,另外一側(cè)會由一個(gè)或者多個(gè)窗口消費(fèi)cost。
- 如果另一側(cè)不能完全消費(fèi)cost,那么增加寬度那側(cè) 也不能增加全部cost
按照上面的約束條件,我們就可以設(shè)計(jì)出拖拽代碼
/***
* widths 是一個(gè)數(shù)組 表示所有窗口的對應(yīng)寬度
* index 表示當(dāng)前拖拽bar的坐標(biāo),由1開始到widths.length-1;
* cost 是一次拖拽距離
**/
function dragResize(widths, index, cost) {
if (cost > 0) { // 正向拖拽
let noCost = cost;// 記錄有多少距離沒有消費(fèi)
for (let i = index; i < widths.length; i++) {
noCost = calcuCost(widths, i, noCost);
if (noCost == 0) break;
}
//另一側(cè)窗口的增加距離為 總消耗 - 未消耗
widths[index - 1] += cost - noCost;
} else if (cost < 0) { // 反向拖拽
cost = -cost;
let noCost = cost;// 記錄有多少距離沒有消費(fèi)
for (let i = index - 1; i > -1; i--) {
noCost = calcuCost(widths, i, noCost);
if (noCost == 0) break;
}
//另一側(cè)窗口的增加距離為 總消耗 - 未消耗
widths[index] += cost - noCost;
}
}
/**
* 計(jì)算消耗 并返回剩余未消耗
*/
function calcuCost(widths, index, noCost) {
let result = widths[index] - noCost;
if (result <= 0) {
// 記錄窗口的消費(fèi) 如果result<=0表示這個(gè)窗口不能消耗完全
noCost = -result; // 剩余多少沒有被消費(fèi)
widths[index] = 0;// 由于消耗不完 所以它的最后寬度變?yōu)?
return noCost;
} else {
//記錄窗口的消費(fèi) 如果result>0 表示到這個(gè)窗口已經(jīng)完全消費(fèi)了拖拽距離
widths[i] = result;// 窗口設(shè)置為剩余的寬度
return 0;
}
}
其實(shí)上下拖拽也是同理的,只要把widths換成height就可以了。
瀏覽器拖拽工具類
正常拖拽設(shè)計(jì)流程就是 mousedown,mousemove,mouseup。mousedown的時(shí)候開始監(jiān)聽mousemove,mouseup的時(shí)候停止監(jiān)聽。
- 如果監(jiān)聽元素mousemove 鼠標(biāo)移出元素之后就會產(chǎn)生事件中斷的問題
所以這里mousemove和mouseup都監(jiān)聽的document的事件。 - 監(jiān)聽document的mousemove 鼠標(biāo)移出到瀏覽器窗口外 ,也會產(chǎn)生事件中斷的問題。
所以可以采用captureEvents。只要調(diào)用了captureEvents 無論鼠標(biāo)是否在窗口內(nèi)都可以監(jiān)聽到。
拖拽所關(guān)心的事情
- 每次移動 鼠標(biāo)當(dāng)前位置與上次位置的距離變化 dx,dy
- 每次移動 從鼠標(biāo)按下到現(xiàn)在總位置變化 px, py
- 何時(shí)停止移動
/**
* 拖拽中回調(diào)函數(shù)
* dx 鼠標(biāo)在x軸的變化距離
* dy 鼠標(biāo)在y軸的變化距離
* px 鼠標(biāo)從開始移動 到現(xiàn)在的總x軸變化距離
* py 鼠標(biāo)從開始移動 到現(xiàn)在的總y軸變化距離
**/
function onDrag(dx,dy,px,py) {
}
/**
* 拖拽結(jié)束回調(diào)函數(shù)
* dx 鼠標(biāo)在x軸的變化距離
* dy 鼠標(biāo)在y軸的變化距離
* px 鼠標(biāo)從開始移動 到現(xiàn)在的總x軸變化距離
* py 鼠標(biāo)從開始移動 到現(xiàn)在的總y軸變化距離
**/
function onDragEnd(dx,dy,px,py) {
}
/**
* 拖拽函數(shù) 調(diào)用時(shí)機(jī)需要在鼠標(biāo)按下時(shí)候
* startX 起始鼠標(biāo)x軸坐標(biāo)
* startY 起始鼠標(biāo)y軸坐標(biāo)
* onDrag 拖拽中監(jiān)聽函數(shù)
* onDragEnd 拖拽結(jié)束監(jiān)聽函數(shù)
*/
function drag(startX, startY, onDrag, onDragEnd) {
let prevX = startX;
let prevY = startY;
function mouseMove(e) {
onDrag(e.pageX - prevX, e.pageY - prevY, e.pageX - startX, e.pageY - startY);
prevX = e.pageX;
prevY = e.pageY;
}
function mouseUp(e) {
onDragEnd(e.pageX - prevX, e.pageY - prevY, e.pageX - startX, e.pageY - startY);
document.releaseEvents(Event.MOUSEMOVE | Event.MOUSEUP);
document.removeEventListener('mousemove', mouseMove);
document.removeEventListener('mouseup', mouseUp);
}
document.captureEvents(Event.MOUSEMOVE | Event.MOUSEUP);
document.addEventListener('mousemove', mouseMove);
document.addEventListener('mouseup', mouseUp);
}
效果圖
這里貼一張效果圖。

效果圖