ホームページ >ウェブフロントエンド >jsチュートリアル >vueを使ったグリッドレイアウト機能の実装方法

vueを使ったグリッドレイアウト機能の実装方法

亚连
亚连オリジナル
2018-06-13 15:52:334089ブラウズ

この記事では主に vue を使用してグリッド レイアウト機能を実装するコードの説明を紹介します。必要な方は参照してください。

1. まず、プロジェクトをローカルにクローンします。

2. git restart --hard commit コマンドは、現在のヘッドが特定のコミットを指すようにすることができます。 git reset --hard commit 命令可以使当前head指向某个commit。

完成html的基本布局

点击复制按钮来复制整个commit id。然后在项目根路径下运行 git reset

HTMLの基本レイアウトを完成させます

コピーボタンをクリックしてコミットID全体をコピーします。次に、プロジェクトのルート パスで git restart を実行します。ブラウザで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を使用してノードの簡単なレイアウトを完成させます

最初にコミットに切り替え、必要なパッケージをインストールします。次のコマンドを実行します:

git reset --hard 83842ea107e7d819761f25bf06bfc545102b2944
npm install
<!-- 启动,端口为7777,在package.json中可以修改 -->
npm start

このステップ 1 では、webpack.config.js 構成ファイルを確認するだけで環境を構築できます。

もう 1 つは、ノードのレイアウトです。各ノードは、横座標 (x) と縦座標 (y) によってノードの位置を制御できます。 (0, 0); ノードのサイズは幅 (w) と高さ (h) によって制御されます。各ノードには一意の ID も必要です。このように、ノードノードのデータ構造は次のようになります。

{
 id: "uuid",
 x: 0,
 y: 0,
 w: 6,
 h: 8
}

w と h の値は、グリッド内のセルの数です。たとえば、コンテナーは 24 セルで、幅は 960px です。各セルの幅が 40 ピクセルの場合、上記のノードは 240 ピクセル * 320 ピクセルとしてコンテナーの左上隅にレンダリングされます。

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 + &#39;px&#39;,
  height: node.h * this.cfg.cellH + &#39;px&#39;,
  transform: "translate("+ node.x * this.cfg.cellW +"px, "+ node.y * this.cfg.cellH +"px)"
 };
 }
}

ここで、cellW と cellH は各グリッドの幅と高さであるため、ノードの幅、高さ、変位を簡単に計算できます。

単一ノードのドラッグを完​​了する

ドラッグイベント

1. ドラッグを実装するには、mousedown、mousemove、mouseupを使用します。

2. これらのイベントはドキュメントにバインドされており、バインドする必要があるのは 1 回だけです。

実行プロセスは大まかに次のとおりです:

マウスがドラッグハンドル上で押されると、onMouseDownメソッドがトリガーされ、eventHandlerにいくつかの値が格納された後、マウスの動きがonMouseMoveメソッドをトリガーします。初回は、eventHandler.drag が false で、そのうち isDrag メソッドは、変位 (水平方向または垂直方向に 5 ピクセル移動) に基づいてドラッグ動作であるかどうかを判断します。ドラッグ動作である場合は、drag 属性を true に設定して実行します。同時に、dragdrop.dragStart メソッドを実行すると (ドラッグ動作は 1 回だけ実行されます)、マウスは動き続け、dragdrop.drag メソッドの実行が開始されます。最後に、マウスが放された後、onMouseUp メソッドが実行されて一部の状態が初期状態にリセットされ、同時に dragdrop.dragEnd メソッドが実行されます。

ノードのドラッグ アンド ドロップ

ノードのドラッグ アンド ドロップのロジックは、dragdrop.js ファイルにカプセル化されています。主なメソッドは、dragStart、drag、dragEnd です。

dragStart

ドラッグ動作では、このメソッドは 1 回だけ実行されるため、初期化作業を行うのに適しています。

dragStart(el, offsetX, offsetY) {
 // 要拖拽的节点
 const dragNode = utils.searchUp(el, &#39;dragrid-item&#39;);
 // 容器
 const dragContainer = utils.searchUp(el, &#39;dragrid&#39;);
 // 拖拽实例
 const instance = cache.get(dragContainer.getAttribute(&#39;name&#39;));
 // 拖拽节点
 const dragdrop = dragContainer.querySelector(&#39;.dragrid-dragdrop&#39;);
 // 拖拽节点id
 const dragNodeId = dragNode.getAttribute(&#39;dg-id&#39;);
 // 设置拖拽节点
 dragdrop.setAttribute(&#39;style&#39;, dragNode.getAttribute(&#39;style&#39;));
 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 パラメーターはドラッグ ハンドルです。要素、offsetX はマウスのドラッグ距離です。ドラッグ ハンドルの水平オフセット、offsetY はドラッグ ハンドルからのマウスの垂直オフセットです。

2. el を通じて、ドラッグ ノード (dragNode) とドラッグ コンテナー (dragContainer) を再帰的に見つけることができます。

3. ドラッグドロップ要素は、実際にマウスで制御されるドラッグ ノードであり、対応するレイアウト ノードは、シャドウ効果として視覚的に表示されるプレースホルダー ノードになります。

4. ドラッグ ノードを設定すると、実際にはクリックされたドラッグノードの innerHTML がドラッグドロップに設定され、スタイルも適用されます。

5. ドラッグインスタンスは実際にはdragrid.vueインスタンスであり、作成されたフック関数のキャッシュにそのインスタンスをキャッシュします。これにより、メソッドを呼び出すことができます。インスタンスで。

6.instance.current =ragNodeId; 設定後、ドラッグドロップノードとプレースホルダーノードのスタイルが適用されます。

7. キャッシュされたデータの offsetX と offsetY は、ノードの左上隅を基準としたドラッグ ハンドルのオフセットです。

drag

ドラッグ動作が発生した後、マウスの移動によりこのメソッドが実行され、ドラッグされたノードのスタイルを常に更新することでノードが移動されます。

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 += &#39;;transform:translate(&#39;+ x +&#39;px, &#39;+ y +&#39;px)&#39;;
}

主にコンテナに対するノードのオフセットを計算します: マウスとページの間の距離 - コンテナのオフセット - マウスとノードの間の距離 ノードとコンテナの間の距離です。ノードとコンテナの間。

dragEnd

は主に状態をリセットすることです。ロジックは比較的単純なので、詳細は説明しません。

この時点で、単一のノードをマウスに従って移動できます。

ドラッグされたノードの動きにプレースホルダーが追従できるようにします

このセクションでは、ドラッグされたノードと一緒に動くプレースホルダー ノード (プレースホルダーの影の部分) について説明します。主なアイデアは次のとおりです:

コンテナからノードのオフセット (ドラッグ メソッドの x、y) をドラッグすることで、対応するグリッドの座標に変換できます。

変換された座標が変化すると、プレースホルダーノードの座標も更新されます。

ドラッグ メソッドに追加されたコードは次のとおりです:

// 坐标转换
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;
}

ノードの再配置と上への移動

このセクションには 2 つの中心点があります:

グリッドを表すために 2 次元配列を使用します。ノードの位置情報は、この 2 次元配列にマークすることができます。

ノード内でノードが変更される限り、再配置し、各ノードを可能な限り上に移動する必要があります。 🎜🎜🎜二次元配列の構築🎜🎜
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 += &#39;;width:&#39; + w + &#39;px;height:&#39; + h + &#39;px;&#39;;
 // 坐标转换
 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. 拖拽控制滚动条。

  3. 拖拽边界的限制。

  4. 向下拖拽,达到碰撞节点1/2高度就发生换位。

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

在JavaScript中如何实现读取和写入cookie

在vue中scroller返回页面并且记住滚动位置如何实现

vue+springboot如何实现单点登录跨域问题(详细教程)

以上がvueを使ったグリッドレイアウト機能の実装方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。