> 웹 프론트엔드 > JS 튜토리얼 > vue를 사용하여 그리드 레이아웃 기능을 구현하는 방법

vue를 사용하여 그리드 레이아웃 기능을 구현하는 방법

亚连
풀어 주다: 2018-06-13 15:52:33
원래의
4057명이 탐색했습니다.

이 글은 주로 vue를 사용하여 그리드 레이아웃 기능을 구현하는 코드 설명을 소개합니다. 필요한 친구가 참고할 수 있습니다.

1. 먼저 프로젝트를 로컬로 복제합니다.

2. git Reset --hard commit 명령은 현재 헤드가 특정 커밋을 가리키도록 할 수 있습니다. git reset --hard commit 命令可以使当前head指向某个commit。

完成html的基本布局

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

html의 기본 레이아웃을 완성합니다

복사 버튼을 클릭하면 전체 커밋 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를 사용하여 노드의 간단한 레이아웃 완성

먼저 커밋으로 전환하고 필요한 패키지를 설치합니다. 그리고 다음 명령을 실행하세요:

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

이 단계 1은 환경을 구축하는 것입니다. 이를 위해 webpack.config.js 구성 파일을 살펴보세요.

다른 하나는 노드의 레이아웃입니다. 주요 아이디어는 노드 컨테이너를 그리드로 간주하는 것입니다. 각 노드는 가로좌표(x)와 세로좌표(y)를 통해 노드의 위치를 ​​제어할 수 있습니다. (0, 0); 노드 크기는 너비(w)와 높이(h)에 의해 제어됩니다. 각 노드에는 고유한 ID도 있어야 합니다. 이런 방식으로 노드 노드의 데이터 구조는 다음과 같습니다.

{
 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 + &#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는 각 그리드의 너비와 높이이므로 노드의 너비, 높이 및 변위를 쉽게 계산할 수 있습니다.

단일 노드 전체 드래그

Drag 이벤트

1. mousedown, mousemove, mouseup을 사용하여 드래그를 구현합니다.

2. 이러한 이벤트는 문서에 바인딩되며 한 번만 바인딩하면 됩니다.

실행 과정은 대략 다음과 같습니다.

드래그 핸들을 마우스로 누르면 onMouseDown 메서드가 트리거됩니다. eventHandler에 일부 값을 저장한 후 마우스 움직임에 대해 onMouseMove 메서드가 트리거됩니다. 처음에는 eventHandler.drag가 false이고, 그 중 isDrag 메소드는 변위(수평 또는 수직으로 5픽셀 이동)를 기준으로 드래그 동작인지 여부를 결정합니다. 드래그 동작인 경우 드래그 속성을 true로 설정하고 실행합니다. dragdrop.dragStart 메소드를 동시에 실행하면(드래그 동작은 한 번만 실행됩니다) 마우스가 계속 움직이고 dragdrop.drag 메소드가 실행되기 시작합니다. 마지막으로 마우스를 놓은 후 onMouseUp 메서드가 실행되어 일부 상태를 초기 상태로 되돌리는 동시에 dragdrop.dragEnd 메서드가 실행됩니다.

드래그 앤 드롭 노드

드래그 앤 드롭 노드의 논리는 dragdrop.js 파일에 캡슐화되어 있습니다. 주요 메소드는 dragStart, drag 및 dragEnd입니다.

dragStart

드래그 동작에서 이 메서드는 한 번만 실행되므로 일부 초기화 작업을 수행하는 데 적합합니다.

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 매개변수 el은 드래그 핸들입니다. 요소, offsetX는 마우스 거리 드래그입니다. 드래그 핸들의 수평 오프셋이고, offsetY는 드래그 핸들에서 마우스의 수직 오프셋입니다.

2.el을 통해 드래그 노드(dragNode)와 드래그 컨테이너(dragContainer)를 재귀적으로 찾을 수 있습니다.

3. 드래그드롭 요소는 실제 마우스로 제어되는 드래그 노드이며 해당 레이아웃 노드는 그림자 효과로 시각적으로 표시되는 자리 표시자 노드가 됩니다.

4. 드래그 노드를 설정하면 실제로 클릭한 dragNode의 innerHTML이 드래그 드롭으로 설정되고 스타일도 적용됩니다.

5. 드래그 인스턴스는 실제로 dragrid.vue 인스턴스입니다. 생성된 후크 함수의 캐시에 해당 인스턴스를 캐시하여 이름에 따라 인스턴스를 가져올 수 있습니다. .

6.instance.current = dragNodeId; 설정 후 드래그드롭 노드와 플레이스홀더 노드의 스타일이 적용됩니다.

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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿