溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

使用vue怎么實(shí)現(xiàn)一個(gè)grid-layout功能

發(fā)布時(shí)間:2021-03-30 15:51:19 來源:億速云 閱讀:291 作者:Leah 欄目:web開發(fā)

使用vue怎么實(shí)現(xiàn)一個(gè)grid-layout功能?針對(duì)這個(gè)問題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問題的小伙伴找到更簡(jiǎn)單易行的方法。

1.先clone項(xiàng)目到本地。

2.git reset --hard commit 命令可以使當(dāng)前head指向某個(gè)commit。

完成html的基本布局

點(diǎn)擊復(fù)制按鈕來復(fù)制整個(gè)commit id。然后在項(xiàng)目根路徑下運(yùn)行 git reset 。用瀏覽器打開index.html來預(yù)覽效果,該插件的html主要結(jié)果如下:

<!-- 節(jié)點(diǎn)容器 -->
<div class="dragrid">
 <!-- 可拖拽的節(jié)點(diǎn),使用translate控制位移 -->
 <div class="dragrid-item" >
 <!-- 通過slot可以插入動(dòng)態(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簡(jiǎn)單排版

先切換commit,安裝需要的包,運(yùn)行如下命令:

git reset --hard 83842ea107e7d819761f25bf06bfc545102b2944
npm install
<!-- 啟動(dòng),端口為7777,在package.json中可以修改 -->
npm start

這一步一個(gè)是搭建環(huán)境,這個(gè)直接看webpack.config.js配置文件就可以了。

另一個(gè)就是節(jié)點(diǎn)的排版(layout),主要思路是把節(jié)點(diǎn)容器看成一個(gè)網(wǎng)格,每個(gè)節(jié)點(diǎn)就可以通過橫坐標(biāo)(x)和縱坐標(biāo)(y)來控制節(jié)點(diǎn)的位置,左上角坐標(biāo)為(0, 0);通過寬(w)和高(h)來控制節(jié)點(diǎn)大?。幻總€(gè)節(jié)點(diǎn)還必須有一個(gè)唯一的id。這樣節(jié)點(diǎn)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é)點(diǎn)渲染為240px * 320px, 且在容器左上角。

來看一下dragrid.vue與之對(duì)應(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為每個(gè)格子的寬和高,這樣計(jì)算節(jié)點(diǎn)的寬和高及位移就很容易了。

完成單個(gè)節(jié)點(diǎn)的拖拽

拖拽事件

1.使用mousedown、mousemove、mouseup來實(shí)現(xiàn)拖拽。

2.這些事件綁定在document上,只需要綁定一次就可以。

執(zhí)行流程大致如下:

鼠標(biāo)在拖拽句柄上按下, onMouseDown 方法觸發(fā),在eventHandler中存儲(chǔ)一些值之后,鼠標(biāo)移動(dòng)則觸發(fā) onMouseMove 方法,第一次進(jìn)入時(shí) eventHandler.drag 為false,其中isDrag方法會(huì)根據(jù)位移來判斷是否是拖拽行為(橫向或縱向移動(dòng)5像素),如果是拖拽行為,則將drag屬性設(shè)置為true,同時(shí)執(zhí)行 dragdrop.dragStart 方法(一次拖拽行為只會(huì)執(zhí)行一次),之后鼠標(biāo)繼續(xù)移動(dòng),則就開始執(zhí)行 dragdrop.drag 方法了。最后鼠標(biāo)松開后,會(huì)執(zhí)行 onMouseUp 方法,將一些狀態(tài)重置回初始狀態(tài),同時(shí)執(zhí)行 dragdrop.dragEnd 方法。

拖拽節(jié)點(diǎn)

拖拽節(jié)點(diǎn)的邏輯都封裝在dragdrop.js這個(gè)文件里,主要方法為 dragStart 、 drag 、 dragEnd 。

dragStart

在一次拖拽行為中,該方法只執(zhí)行一次,因此適合做一些初始化工作,此時(shí)代碼如下:

dragStart(el, offsetX, offsetY) {
 // 要拖拽的節(jié)點(diǎn)
 const dragNode = utils.searchUp(el, 'dragrid-item');
 // 容器
 const dragContainer = utils.searchUp(el, 'dragrid');
 // 拖拽實(shí)例
 const instance = cache.get(dragContainer.getAttribute('name'));
 // 拖拽節(jié)點(diǎn)
 const dragdrop = dragContainer.querySelector('.dragrid-dragdrop');
 // 拖拽節(jié)點(diǎn)id
 const dragNodeId = dragNode.getAttribute('dg-id');
 // 設(shè)置拖拽節(jié)點(diǎn)
 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é)點(diǎn)(dragNode),及拖拽容器(dragContainer)。

3.dragdrop元素是真正鼠標(biāo)控制拖拽的節(jié)點(diǎn),同時(shí)與之對(duì)應(yīng)的布局節(jié)點(diǎn)會(huì)變?yōu)檎嘉还?jié)點(diǎn)(placeholder),視覺上顯示為陰影效果。

4.設(shè)置拖拽節(jié)點(diǎn)其實(shí)就將點(diǎn)擊的dragNode的innerHTML設(shè)置到dragdrop中,同時(shí)將樣式也應(yīng)用過去。

5.拖拽實(shí)例,其實(shí)就是dragrid.vue實(shí)例,它在created鉤子函數(shù)中將其實(shí)例緩存到cache中,在這里根據(jù)name就可以從cache中得到該實(shí)例,從而可以調(diào)用該實(shí)例中的方法了。

6.instance.current = dragNodeId; 設(shè)置之后,dragdrop節(jié)點(diǎn)及placeholder節(jié)點(diǎn)的樣式就應(yīng)用了。

7.緩存數(shù)據(jù)中的offsetX、offsetY是拖拽句柄相對(duì)于節(jié)點(diǎn)左上角的偏移。

drag

發(fā)生拖拽行為之后,鼠標(biāo)move都會(huì)執(zhí)行該方法,通過不斷更新拖拽節(jié)點(diǎn)的樣式來是節(jié)點(diǎn)發(fā)生移動(dòng)效果。

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)';
}

主要是計(jì)算節(jié)點(diǎn)相對(duì)于容器的偏移:鼠標(biāo)距離頁(yè)面距離-容器偏移-鼠標(biāo)距離拽節(jié)點(diǎn)距離就為節(jié)點(diǎn)距離容器的距離。

dragEnd

主要是重置狀態(tài)。邏輯比較簡(jiǎn)單,就不再細(xì)說了。

到這里已經(jīng)單個(gè)節(jié)點(diǎn)已經(jīng)可以跟隨鼠標(biāo)進(jìn)行移動(dòng)了。

使placeholder可以跟隨拖拽節(jié)點(diǎn)運(yùn)動(dòng)

本節(jié)是要講占位節(jié)點(diǎn)(placeholder陰影部分)跟隨拖拽節(jié)點(diǎn)一起移動(dòng)。主要思路是:

通過拖拽節(jié)點(diǎn)距離容器的偏移(drag方法中的x, y),可以將其轉(zhuǎn)化為對(duì)應(yīng)網(wǎng)格的坐標(biāo)。

轉(zhuǎn)化后的坐標(biāo)如果發(fā)生變化,則更新占位節(jié)點(diǎn)的坐標(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ā)生移動(dòng)
if(currentNode.x !== nodeX || currentNode.y !== nodeY) {
 currentNode.x = nodeX;
 currentNode.y = nodeY;
}

nodes重排及上移

本節(jié)核心點(diǎn)有兩個(gè):

用一個(gè)二維數(shù)組來表示網(wǎng)格,這樣節(jié)點(diǎn)的位置信息就可以在此二維數(shù)組中標(biāo)記出來了。

nodes中只要某個(gè)節(jié)點(diǎn)發(fā)生變化,就要重新排版,要將每個(gè)節(jié)點(diǎn)盡可能地上移。

二維數(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;
}

按需可以動(dòng)態(tài)擴(kuò)展該二維數(shù)據(jù),如果某行沒有任何節(jié)點(diǎn)占位,則實(shí)際存儲(chǔ)的是一個(gè)undefined值。否則存儲(chǔ)的是節(jié)點(diǎn)的id值。

布局方法

dragird.vue中watch了nodes,發(fā)生變化后會(huì)調(diào)用layout方法,代碼如下:

/**
 * 重新布局
 * 只要有一個(gè)節(jié)點(diǎn)發(fā)生變化,就要重新進(jìn)行排版布局
 */
layout() {
 this.nodes.forEach(n => {
 const y = this.moveup(n);
 if(y < n.y){
  n.y = y;
 }
 });
},
// 向上查找節(jié)點(diǎn)可以冒泡到的位置
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é)點(diǎn),moveup方法返回該節(jié)點(diǎn)縱向可以上升到的位置坐標(biāo),如果比實(shí)際坐標(biāo)小,則進(jìn)行上移。moveup方法默認(rèn)從上一行開始找,直到發(fā)現(xiàn)二維數(shù)組中存放了值(改行已經(jīng)有元素了),則返回此時(shí)行數(shù)加1。

到這里,拖拽節(jié)點(diǎn)移動(dòng)時(shí),占位節(jié)點(diǎn)會(huì)盡可能地上移,如果只有一個(gè)節(jié)點(diǎn),那么占位節(jié)點(diǎn)一直在最上面移動(dòng)。

相關(guān)節(jié)點(diǎn)的下移

拖拽節(jié)點(diǎn)移動(dòng)時(shí),與拖拽節(jié)點(diǎn)發(fā)生碰撞的節(jié)點(diǎn)及其下發(fā)的節(jié)點(diǎn),都先下移一定距離,這樣拖拽節(jié)點(diǎn)就可以移到相應(yīng)位置,最后節(jié)點(diǎn)都會(huì)發(fā)生上一節(jié)所說的上移。

請(qǐng)看dragrid.vue中的overlap方法:

overlap(node) {
 // 下移節(jié)點(diǎn)
 this.nodes.forEach(n => {
 if(node !== n && n.y + n.h > node.y) {
  n.y += node.h;
 }
 });
}

n.y + n.h > node.y 表示可以與拖拽節(jié)點(diǎn)發(fā)生碰撞,以及在拖拽節(jié)點(diǎn)下方的節(jié)點(diǎn)。

在dragdrop.drag中會(huì)調(diào)用該方法。

注意目前該方法會(huì)有問題,沒有考慮到如果碰撞節(jié)點(diǎn)比較高,則 n.y += node.h 并沒有將該節(jié)點(diǎn)下沉到拖拽節(jié)點(diǎn)下方,從而拖拽節(jié)點(diǎn)會(huì)疊加上去。后面會(huì)介紹解決方法。

縮放

上面的思路都理解之后,縮放其實(shí)也是一樣的,主要還是要進(jìn)行坐標(biāo)轉(zhuǎn)換,坐標(biāo)發(fā)生變化后,就會(huì)調(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é)點(diǎn)寬和高
 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ā)生移動(dòng)
 if(currentNode.w !== nodeW || currentNode.h !== nodeH) {
  currentNode.w = nodeW;
  currentNode.h = nodeH;
  this.dragrid.overlap(currentNode);
 }
}

根據(jù)鼠標(biāo)距拖拽容器的距離的偏移,來修改節(jié)點(diǎn)的大小(寬和高),其中x1為鼠標(biāo)點(diǎn)擊后距離容器的距離,x2為移動(dòng)一段距離之后距離容器的距離,那么差值dx就為鼠標(biāo)移動(dòng)的距離,dy同理。

到這里,插件的核心邏輯基本上已經(jīng)完成了。

[fix]解決碰撞位置靠上的大塊,并沒有下移的問題

overlap修改為:

overlap(node) {
 let offsetUpY = 0;
 // 碰撞檢測(cè),查找一起碰撞節(jié)點(diǎn)里面,位置最靠上的那個(gè)
 this.nodes.forEach(n => {
 if(node !== n && this.checkHit(node, n)){
  const value = node.y - n.y;
  offsetUpY = value > offsetUpY ? value : offsetUpY;
 }
 });
 // 下移節(jié)點(diǎn)
 this.nodes.forEach(n => {
 if(node !== n && n.y + n.h > node.y) {
  n.y += (node.h + offsetUpY);
 }
 });
}

offsetUpY 最終存放的是與拖拽節(jié)點(diǎn)發(fā)生碰撞的所有節(jié)點(diǎn)中,位置最靠上的節(jié)點(diǎn)與拖拽節(jié)點(diǎn)之間的距離。然后再下移過程中會(huì)加上該offsetUpY值,確保所有節(jié)點(diǎn)下移到拖拽節(jié)點(diǎn)下方。

這個(gè)插件的核心邏輯就說到這里了,讀者可以自己解決如下一些問題:

  1. 縮放限制,達(dá)到最小寬度就不能再繼續(xù)縮放了。

  2. 拖拽控制滾動(dòng)條。

  3. 拖拽邊界的限制。

  4. 向下拖拽,達(dá)到碰撞節(jié)點(diǎn)1/2高度就發(fā)生換位。

關(guān)于使用vue怎么實(shí)現(xiàn)一個(gè)grid-layout功能問題的解答就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識(shí)。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

vue
AI