1.先clone項目到本地。
2.git reset --hard commit
命令可以使當(dāng)前head指向某個commit。
完成html的基本布局
點擊復(fù)制按鈕來復(fù)制整個commit id。然后在項目根路徑下運行 git reset
。用瀏覽器打開index.html來預(yù)覽效果,該插件的html主要結(jié)果如下:
<!-- 節(jié)點容器 --><div class="dragrid"> <!-- 可拖拽的節(jié)點,使用translate控制位移 --> <div class="dragrid-item" style="transform: translate(0px, 0px)"> <!-- 通過slot可以插入動態(tài)內(nèi)容 --> <div class="dragrid-item-content"> </div> <!-- 拖拽句柄 --> <div class="dragrid-drag-bar"></div> <!-- 縮放句柄 --> <div class="dragrid-resize-bar"></div> </div></div>
使用vue完成nodes簡單排版
先切換commit,安裝需要的包,運行如下命令:
git reset --hard 83842ea107e7d819761f25bf06bfc545102b2944npm install<!-- 啟動,端口為7777,在package.json中可以修改 -->npm start
這一步一個是搭建環(huán)境,這個直接看webpack.config.js配置文件就可以了。
另一個就是節(jié)點的排版(layout),主要思路是把節(jié)點容器看成一個網(wǎng)格,每個節(jié)點就可以通過橫坐標(biāo)(x)和縱坐標(biāo)(y)來控制節(jié)點的位置,左上角坐標(biāo)為(0, 0);通過寬(w)和高(h)來控制節(jié)點大小;每個節(jié)點還必須有一個唯一的id。這樣節(jié)點node的數(shù)據(jù)結(jié)構(gòu)就為:
{ id: "uuid", x: 0, y: 0, w: 6, h: 8}
其中w和h的值為所占網(wǎng)格的格數(shù),例如容器是24格,且寬度為960px,每格寬度就為40px,則上面節(jié)點渲染為240px * 320px, 且在容器左上角。
來看一下dragrid.vue與之對應(yīng)的邏輯:
computed: { cfg() { let cfg = Object.assign({}, config); cfg.cellW = Math.floor(this.containerWidth / cfg.col); cfg.cellH = cfg.cellW; // 1:1 return cfg; }},methods: { getStyle(node) { return { width: node.w * this.cfg.cellW + 'px', height: node.h * this.cfg.cellH + 'px', transform: "translate("+ node.x * this.cfg.cellW +"px, "+ node.y * this.cfg.cellH +"px)" }; }}
其中cellW、cellH為每個格子的寬和高,這樣計算節(jié)點的寬和高及位移就很容易了。
完成單個節(jié)點的拖拽
拖拽事件
1.使用mousedown、mousemove、mouseup來實現(xiàn)拖拽。
2.這些事件綁定在document上,只需要綁定一次就可以。
執(zhí)行流程大致如下:
鼠標(biāo)在拖拽句柄上按下, onMouseDown 方法觸發(fā),在eventHandler中存儲一些值之后,鼠標(biāo)移動則觸發(fā) onMouseMove 方法,第一次進入時 eventHandler.drag 為false,其中isDrag方法會根據(jù)位移來判斷是否是拖拽行為(橫向或縱向移動5像素),如果是拖拽行為,則將drag屬性設(shè)置為true,同時執(zhí)行 dragdrop.dragStart 方法(一次拖拽行為只會執(zhí)行一次),之后鼠標(biāo)繼續(xù)移動,則就開始執(zhí)行 dragdrop.drag 方法了。最后鼠標(biāo)松開后,會執(zhí)行 onMouseUp 方法,將一些狀態(tài)重置回初始狀態(tài),同時執(zhí)行 dragdrop.dragEnd 方法。
拖拽節(jié)點
拖拽節(jié)點的邏輯都封裝在dragdrop.js這個文件里,主要方法為 dragStart 、 drag 、 dragEnd 。
dragStart
在一次拖拽行為中,該方法只執(zhí)行一次,因此適合做一些初始化工作,此時代碼如下:
dragStart(el, offsetX, offsetY) { // 要拖拽的節(jié)點 const dragNode = utils.searchUp(el, 'dragrid-item'); // 容器 const dragContainer = utils.searchUp(el, 'dragrid'); // 拖拽實例 const instance = cache.get(dragContainer.getAttribute('name')); // 拖拽節(jié)點 const dragdrop = dragContainer.querySelector('.dragrid-dragdrop'); // 拖拽節(jié)點id const dragNodeId = dragNode.getAttribute('dg-id'); // 設(shè)置拖拽節(jié)點 dragdrop.setAttribute('style', dragNode.getAttribute('style')); dragdrop.innerHTML = dragNode.innerHTML; instance.current = dragNodeId; const offset = utils.getOffset(el, dragNode, {offsetX, offsetY}); // 容器偏移 const containerOffset = dragContainer.getBoundingClientRect(); // 緩存數(shù)據(jù) this.offsetX = offset.offsetX; this.offsetY = offset.offsetY; this.dragrid = instance; this.dragElement = dragdrop; this.dragContainer = dragContainer; this.containerOffset = containerOffset;}
1.參數(shù)el為拖拽句柄元素,offsetX為鼠標(biāo)距離拖拽句柄的橫向偏移,offsetY為鼠標(biāo)距離拖拽句柄的縱向偏移。
2.通過el可以向上遞歸查找到拖拽節(jié)點(dragNode),及拖拽容器(dragContainer)。
3.dragdrop元素是真正鼠標(biāo)控制拖拽的節(jié)點,同時與之對應(yīng)的布局節(jié)點會變?yōu)檎嘉还?jié)點(placeholder),視覺上顯示為陰影效果。
4.設(shè)置拖拽節(jié)點其實就將點擊的dragNode的innerHTML設(shè)置到dragdrop中,同時將樣式也應(yīng)用過去。
5.拖拽實例,其實就是dragrid.vue實例,它在created鉤子函數(shù)中將其實例緩存到cache中,在這里根據(jù)name就可以從cache中得到該實例,從而可以調(diào)用該實例中的方法了。
6.instance.current = dragNodeId; 設(shè)置之后,dragdrop節(jié)點及placeholder節(jié)點的樣式就應(yīng)用了。
7.緩存數(shù)據(jù)中的offsetX、offsetY是拖拽句柄相對于節(jié)點左上角的偏移。
drag
發(fā)生拖拽行為之后,鼠標(biāo)move都會執(zhí)行該方法,通過不斷更新拖拽節(jié)點的樣式來是節(jié)點發(fā)生移動效果。
drag(event) { const pageX = event.pageX, pageY = event.pageY; const x = pageX - this.containerOffset.left - this.offsetX, y = pageY - this.containerOffset.top - this.offsetY; this.dragElement.style.cssText += ';transform:translate('+ x +'px, '+ y +'px)';}
主要是計算節(jié)點相對于容器的偏移:鼠標(biāo)距離頁面距離-容器偏移-鼠標(biāo)距離拽節(jié)點距離就為節(jié)點距離容器的距離。
dragEnd
主要是重置狀態(tài)。邏輯比較簡單,就不再細說了。
到這里已經(jīng)單個節(jié)點已經(jīng)可以跟隨鼠標(biāo)進行移動了。
使placeholder可以跟隨拖拽節(jié)點運動
本節(jié)是要講占位節(jié)點(placeholder陰影部分)跟隨拖拽節(jié)點一起移動。主要思路是:
通過拖拽節(jié)點距離容器的偏移(drag方法中的x, y),可以將其轉(zhuǎn)化為對應(yīng)網(wǎng)格的坐標(biāo)。
轉(zhuǎn)化后的坐標(biāo)如果發(fā)生變化,則更新占位節(jié)點的坐標(biāo)。
drag方法中增加的代碼如下:
// 坐標(biāo)轉(zhuǎn)換const nodeX = Math.round(x / opt.cellW);const nodeY = Math.round(y / opt.cellH);let currentNode = this.dragrid.currentNode;// 發(fā)生移動if(currentNode.x !== nodeX || currentNode.y !== nodeY) { currentNode.x = nodeX; currentNode.y = nodeY;}
nodes重排及上移
本節(jié)核心點有兩個:
用一個二維數(shù)組來表示網(wǎng)格,這樣節(jié)點的位置信息就可以在此二維數(shù)組中標(biāo)記出來了。
nodes中只要某個節(jié)點發(fā)生變化,就要重新排版,要將每個節(jié)點盡可能地上移。
二維數(shù)組的構(gòu)建
getArea(nodes) { let area = []; nodes.forEach(n => { for(let row = n.y; row < n.y + n.h; row++){ let rowArr = area[row]; if(rowArr === undefined){ area[row] = new Array(); } for(let col = n.x; col < n.x + n.w; col++){ area[row][col] = n.id; } } }); return area;}
按需可以動態(tài)擴展該二維數(shù)據(jù),如果某行沒有任何節(jié)點占位,則實際存儲的是一個undefined值。否則存儲的是節(jié)點的id值。
布局方法
dragird.vue中watch了nodes,發(fā)生變化后會調(diào)用layout方法,代碼如下:
/** * 重新布局 * 只要有一個節(jié)點發(fā)生變化,就要重新進行排版布局 */layout() { this.nodes.forEach(n => { const y = this.moveup(n); if(y < n.y){ n.y = y; } });},// 向上查找節(jié)點可以冒泡到的位置moveup(node) { let area = this.area; for(let row = node.y - 1; row > 0; row--){ // 如果一整行都為空,則直接繼續(xù)往上找 if(area[row] === undefined) continue; for(let col = node.x; col < node.x + node.w; col++){ // 改行如果有內(nèi)容,則直接返回下一行 if(area[row][col] !== undefined){ return row + 1; } } } return 0;}
布局方法layout中遍歷所有節(jié)點,moveup方法返回該節(jié)點縱向可以上升到的位置坐標(biāo),如果比實際坐標(biāo)小,則進行上移。moveup方法默認從上一行開始找,直到發(fā)現(xiàn)二維數(shù)組中存放了值(改行已經(jīng)有元素了),則返回此時行數(shù)加1。
到這里,拖拽節(jié)點移動時,占位節(jié)點會盡可能地上移,如果只有一個節(jié)點,那么占位節(jié)點一直在最上面移動。
相關(guān)節(jié)點的下移
拖拽節(jié)點移動時,與拖拽節(jié)點發(fā)生碰撞的節(jié)點及其下發(fā)的節(jié)點,都先下移一定距離,這樣拖拽節(jié)點就可以移到相應(yīng)位置,最后節(jié)點都會發(fā)生上一節(jié)所說的上移。
請看dragrid.vue中的overlap方法:
overlap(node) { // 下移節(jié)點 this.nodes.forEach(n => { if(node !== n && n.y + n.h > node.y) { n.y += node.h; } });}
n.y + n.h > node.y 表示可以與拖拽節(jié)點發(fā)生碰撞,以及在拖拽節(jié)點下方的節(jié)點。
在dragdrop.drag中會調(diào)用該方法。
注意目前該方法會有問題,沒有考慮到如果碰撞節(jié)點比較高,則 n.y += node.h 并沒有將該節(jié)點下沉到拖拽節(jié)點下方,從而拖拽節(jié)點會疊加上去。后面會介紹解決方法。
縮放
上面的思路都理解之后,縮放其實也是一樣的,主要還是要進行坐標(biāo)轉(zhuǎn)換,坐標(biāo)發(fā)生變化后,就會調(diào)用overlap方法。
resize(event) { const opt = this.dragrid.cfg; // 之前 const x1 = this.currentNode.x * opt.cellW + this.offsetX, y1 = this.currentNode.y * opt.cellH + this.offsetY; // 之后 const x2 = event.pageX - this.containerOffset.left, y2 = event.pageY - this.containerOffset.top; // 偏移 const dx = x2 - x1, dy = y2 - y1; // 新的節(jié)點寬和高 const w = this.currentNode.w * opt.cellW + dx, h = this.currentNode.h * opt.cellH + dy; // 樣式設(shè)置 this.dragElement.style.cssText += ';width:' + w + 'px;height:' + h + 'px;'; // 坐標(biāo)轉(zhuǎn)換 const nodeW = Math.round(w / opt.cellW); const nodeH = Math.round(h / opt.cellH); let currentNode = this.dragrid.currentNode; // 發(fā)生移動 if(currentNode.w !== nodeW || currentNode.h !== nodeH) { currentNode.w = nodeW; currentNode.h = nodeH; this.dragrid.overlap(currentNode); }}
根據(jù)鼠標(biāo)距拖拽容器的距離的偏移,來修改節(jié)點的大小(寬和高),其中x1為鼠標(biāo)點擊后距離容器的距離,x2為移動一段距離之后距離容器的距離,那么差值dx就為鼠標(biāo)移動的距離,dy同理。
到這里,插件的核心邏輯基本上已經(jīng)完成了。
[fix]解決碰撞位置靠上的大塊,并沒有下移的問題
overlap修改為:
overlap(node) { let offsetUpY = 0; // 碰撞檢測,查找一起碰撞節(jié)點里面,位置最靠上的那個 this.nodes.forEach(n => { if(node !== n && this.checkHit(node, n)){ const value = node.y - n.y; offsetUpY = value > offsetUpY ? value : offsetUpY; } }); // 下移節(jié)點 this.nodes.forEach(n => { if(node !== n && n.y + n.h > node.y) { n.y += (node.h + offsetUpY); } });}
offsetUpY 最終存放的是與拖拽節(jié)點發(fā)生碰撞的所有節(jié)點中,位置最靠上的節(jié)點與拖拽節(jié)點之間的距離。然后再下移過程中會加上該offsetUpY值,確保所有節(jié)點下移到拖拽節(jié)點下方。
這個插件的核心邏輯就說到這里了,讀者可以自己解決如下一些問題:
總結(jié)
以上所述是小編給大家介紹的使用vue實現(xiàn)grid-layout功能,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對武林網(wǎng)網(wǎng)站的支持!
新聞熱點
疑難解答