這篇文章主要介紹了使用vue實作grid-layout功能的程式碼講解,需要的朋友可以參考下
1.先clone專案到本地。
2.git reset --hard commit
指令可以讓目前head指向某個commit。
完成html的基本佈局
點擊複製按鈕來複製整個commit id。然後在專案根路徑下運行 git reset
。用瀏覽器開啟index.html來預覽效果,外掛程式的html主要結果如下:
<!-- 节点容器 --> <p class="dragrid"> <!-- 可拖拽的节点,使用translate控制位移 --> <p class="dragrid-item" style="transform: translate(0px, 0px)"> <!-- 通过slot可以插入动态内容 --> <p class="dragrid-item-content"> </p> <!-- 拖拽句柄 --> <p class="dragrid-drag-bar"></p> <!-- 缩放句柄 --> <p class="dragrid-resize-bar"></p> </p> </p>
#使用vue完成nodes簡單排版
先切換commit,安裝需要的套件,執行以下指令:
git reset --hard 83842ea107e7d819761f25bf06bfc545102b2944 npm install <!-- 启动,端口为7777,在package.json中可以修改 --> npm start
這一步一個是搭建環境,這個直接看webpack.config.js設定檔就可以了。
另一個就是節點的排版(layout),主要想法是把節點容器看成一個網格,每個節點就可以透過橫座標(x)和縱座標(y)來控制節點的位置,左上角座標為(0, 0);透過寬(w)和高(h)來控制節點大小;每個節點也必須有一個唯一的id。這樣節點node的資料結構就為:
{ id: "uuid", x: 0, y: 0, w: 6, h: 8 }
其中w和h的值為所佔網格的格數,例如容器是24格,且寬度為960px,每格寬度就為40px,則上面節點渲染為240px * 320px, 且在容器左上角。
來看dragrid.vue與之對應的邏輯:
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為每個格子的寬和高,這樣計算節點的寬和高及位移就很容易了。
完成單一節點的拖曳
拖曳事件
1.使用mousedown、mousemove、mouseup來實現拖曳拽。
2.這些事件綁定在document上,只需要綁定一次就可以。
執行流程大致如下:
滑鼠在拖曳句柄上按下,onMouseDown 方法觸發,在eventHandler中儲存一些值之後,滑鼠移動則觸發onMouseMove 方法,第一次進入時eventHandler.drag 為false,其中isDrag方法會根據位移來判斷是否是拖曳行為(橫向或縱向移動5像素),如果是拖曳行為,則將drag屬性設為true,同時執行dragdrop.dragStart 方法(一次拖曳行為只會執行一次),之後滑鼠繼續移動,就開始執行dragdrop.drag 方法了。最後滑鼠放開後,會執行 onMouseUp 方法,將一些狀態重設為初始狀態,同時執行 dragdrop.dragEnd 方法。
拖曳節點
拖曳節點的邏輯都封裝在dragdrop.js這個檔案裡,主要方法是 dragStart 、 drag 、 dragEnd 。
dragStart
在一次拖曳行為中,該方法只執行一次,因此適合做一些初始化工作,此時程式碼如下:
dragStart(el, offsetX, offsetY) { // 要拖拽的节点 const dragNode = utils.searchUp(el, 'dragrid-item'); // 容器 const dragContainer = utils.searchUp(el, 'dragrid'); // 拖拽实例 const instance = cache.get(dragContainer.getAttribute('name')); // 拖拽节点 const dragdrop = dragContainer.querySelector('.dragrid-dragdrop'); // 拖拽节点id const dragNodeId = dragNode.getAttribute('dg-id'); // 设置拖拽节点 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(); // 缓存数据 this.offsetX = offset.offsetX; this.offsetY = offset.offsetY; this.dragrid = instance; this.dragElement = dragdrop; this.dragContainer = dragContainer; this.containerOffset = containerOffset; }
1.參數el為拖曳句柄元素,offsetX為滑鼠距離拖曳句柄的橫向偏移,offsetY為滑鼠距離拖曳句柄的縱向偏移。
2.透過el可以向上遞歸查找到拖曳節點(dragNode),及拖曳容器(dragContainer)。
3.dragdrop元素是真正滑鼠控制拖曳的節點,同時與之對應的佈局節點會變成佔位節點(placeholder),視覺上顯示為陰影效果。
4.設定拖曳節點其實就將點選的dragNode的innerHTML設定到dragdrop中,同時將樣式也套用過去。
5.拖曳實例,其實就是dragrid.vue實例,它在created鉤子函數中將其實例緩存到cache中,在這裡根據name就可以從cache中得到該實例,從而可以調用該實例中的方法了。
6.instance.current = dragNodeId; 設定之後,dragdrop節點及placeholder節點的樣式就套用了。
7.快取資料中的offsetX、offsetY是拖曳句柄相對於節點左上角的偏移。
drag
發生拖曳行為之後,滑鼠move都會執行該方法,透過不斷更新拖曳節點的樣式來是節點發生移動效果。
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)'; }
主要是計算節點相對於容器的偏移:滑鼠距離頁面距離-容器偏移-滑鼠距離拽節點距離就為節點距離容器的距離。
dragEnd
主要是重置狀態。邏輯比較簡單,就不再細說了。
到這裡已經單一節點已經可以跟著滑鼠進行移動了。
使placeholder可以跟隨拖曳節點運動
本節是要講佔位節點(placeholder陰影部分)跟隨拖曳節點一起移動。主要想法是:
透過拖曳節點距離容器的偏移(drag方法中的x, y),可以將其轉換為對應網格的座標。
轉換後的座標如果發生變化,則更新佔位節點的座標。
drag方法中增加的程式碼如下:
// 坐标转换 const nodeX = Math.round(x / opt.cellW); const nodeY = Math.round(y / opt.cellH); let currentNode = this.dragrid.currentNode; // 发生移动 if(currentNode.x !== nodeX || currentNode.y !== nodeY) { currentNode.x = nodeX; currentNode.y = nodeY; }
nodes重排及上移
本節核心點有兩個:
用一個二維數組來表示網格,這樣節點的位置資訊就可以在此二維數組中標記出來了。
nodes中只要某個節點發生變化,就要重新排版,要將每個節點盡可能地移。
二維陣列的建構
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; }
按需可以动态扩展该二维数据,如果某行没有任何节点占位,则实际存储的是一个undefined值。否则存储的是节点的id值。
布局方法
dragird.vue中watch了nodes,发生变化后会调用layout方法,代码如下:
/** * 重新布局 * 只要有一个节点发生变化,就要重新进行排版布局 */ layout() { this.nodes.forEach(n => { const y = this.moveup(n); if(y < n.y){ n.y = y; } }); }, // 向上查找节点可以冒泡到的位置 moveup(node) { let area = this.area; for(let row = node.y - 1; row > 0; row--){ // 如果一整行都为空,则直接继续往上找 if(area[row] === undefined) continue; for(let col = node.x; col < node.x + node.w; col++){ // 改行如果有内容,则直接返回下一行 if(area[row][col] !== undefined){ return row + 1; } } } return 0; }
布局方法layout中遍历所有节点,moveup方法返回该节点纵向可以上升到的位置坐标,如果比实际坐标小,则进行上移。moveup方法默认从上一行开始找,直到发现二维数组中存放了值(改行已经有元素了),则返回此时行数加1。
到这里,拖拽节点移动时,占位节点会尽可能地上移,如果只有一个节点,那么占位节点一直在最上面移动。
相关节点的下移
拖拽节点移动时,与拖拽节点发生碰撞的节点及其下发的节点,都先下移一定距离,这样拖拽节点就可以移到相应位置,最后节点都会发生上一节所说的上移。
请看dragrid.vue中的overlap方法:
overlap(node) { // 下移节点 this.nodes.forEach(n => { if(node !== n && n.y + n.h > node.y) { n.y += node.h; } }); }
n.y + n.h > node.y 表示可以与拖拽节点发生碰撞,以及在拖拽节点下方的节点。
在dragdrop.drag中会调用该方法。
注意目前该方法会有问题,没有考虑到如果碰撞节点比较高,则 n.y += node.h 并没有将该节点下沉到拖拽节点下方,从而拖拽节点会叠加上去。后面会介绍解决方法。
缩放
上面的思路都理解之后,缩放其实也是一样的,主要还是要进行坐标转换,坐标发生变化后,就会调用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; // 新的节点宽和高 const w = this.currentNode.w * opt.cellW + dx, h = this.currentNode.h * opt.cellH + dy; // 样式设置 this.dragElement.style.cssText += ';width:' + w + 'px;height:' + h + 'px;'; // 坐标转换 const nodeW = Math.round(w / opt.cellW); const nodeH = Math.round(h / opt.cellH); let currentNode = this.dragrid.currentNode; // 发生移动 if(currentNode.w !== nodeW || currentNode.h !== nodeH) { currentNode.w = nodeW; currentNode.h = nodeH; this.dragrid.overlap(currentNode); } }
根据鼠标距拖拽容器的距离的偏移,来修改节点的大小(宽和高),其中x1为鼠标点击后距离容器的距离,x2为移动一段距离之后距离容器的距离,那么差值dx就为鼠标移动的距离,dy同理。
到这里,插件的核心逻辑基本上已经完成了。
[fix]解决碰撞位置靠上的大块,并没有下移的问题
overlap修改为:
overlap(node) { let offsetUpY = 0; // 碰撞检测,查找一起碰撞节点里面,位置最靠上的那个 this.nodes.forEach(n => { if(node !== n && this.checkHit(node, n)){ const value = node.y - n.y; offsetUpY = value > offsetUpY ? value : offsetUpY; } }); // 下移节点 this.nodes.forEach(n => { if(node !== n && n.y + n.h > node.y) { n.y += (node.h + offsetUpY); } }); }
offsetUpY 最终存放的是与拖拽节点发生碰撞的所有节点中,位置最靠上的节点与拖拽节点之间的距离。然后再下移过程中会加上该offsetUpY值,确保所有节点下移到拖拽节点下方。
这个插件的核心逻辑就说到这里了,读者可以自己解决如下一些问题:
缩放限制,达到最小宽度就不能再继续缩放了。
拖拽控制滚动条。
拖拽边界的限制。
向下拖拽,达到碰撞节点1/2高度就发生换位。
上面是我整理给大家的,希望今后会对大家有帮助。
相关文章:
vue+springboot如何实现单点登录跨域问题(详细教程)
以上是使用vue如何實作grid-layout功能的詳細內容。更多資訊請關注PHP中文網其他相關文章!