This article mainly introduces the code explanation of using vue to implement the grid-layout function. Friends in need can refer to
1. First clone the project locally.
2.git reset --hard commit
command can make the current head point to a commit.
Complete the basic layout of html
Click the copy button to copy the entire commit id. Then rungit reset
in the project root path. Use a browser to open index.html to preview the effect. The main results of the plug-in's HTML are as follows:
Use vue to complete simple layout of nodes
Switch commit first, installation required package, run the following command:
git reset --hard 83842ea107e7d819761f25bf06bfc545102b2944 npm install npm start
The first step is to set up the environment. For this, just look at the webpack.config.js configuration file.
The other is the layout of nodes. The main idea is to regard the node container as a grid. Each node can control the position of the node through the abscissa (x) and ordinate (y). , the upper left corner coordinate is (0, 0); the node size is controlled by width (w) and height (h); each node must also have a unique id. In this way, the data structure of the node node is:
{ id: "uuid", x: 0, y: 0, w: 6, h: 8 }
The values of w and h are the number of cells in the grid. For example, the container is 24 cells and the width is 960px, and the width of each cell is 40px, then The above node is rendered as 240px * 320px, and is in the upper left corner of the container.
Let’s take a look at the corresponding logic of 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)" }; } }
where cellW and cellH are the width and height of each grid, so it is easy to calculate the width, height and displacement of the node. .
Complete the dragging of a single node
Drag event
1. Use mousedown, mousemove, mouseup to realize dragging Pull.
2. These events are bound to the document and only need to be bound once.
The execution process is roughly as follows:
When the mouse is pressed on the drag handle, the onMouseDown method is triggered. After some values are stored in the eventHandler, the mouse movement triggers the onMouseMove method. When entering for the first time eventHandler.drag is false, and the isDrag method will determine whether it is a dragging behavior based on the displacement (move 5 pixels horizontally or vertically). If it is a dragging behavior, set the drag attribute to true and execute the dragdrop.dragStart method (once The dragging behavior will only be executed once), and then the mouse continues to move, and the dragdrop.drag method begins to be executed. Finally, after the mouse is released, the onMouseUp method will be executed to reset some states back to the initial state, and the dragdrop.dragEnd method will be executed at the same time.
Drag Node
The logic of drag and drop nodes is encapsulated in the dragdrop.js file. The main methods are dragStart, drag, and dragEnd.
dragStart
In a dragging behavior, this method is only executed once, so it is suitable for doing some initialization work. At this time, the code is as follows:
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. The parameter el is the drag handle element, offsetX is the horizontal offset of the mouse from the drag handle, and offsetY is the vertical offset of the mouse from the drag handle.
2. Through el, you can recursively find the drag node (dragNode) and the drag container (dragContainer).
3. The dragdrop element is a real mouse-controlled dragging node, and the corresponding layout node will become a placeholder node, which is visually displayed as a shadow effect.
4. Setting the drag node actually sets the innerHTML of the clicked dragNode to the dragdrop, and also applies the style to it.
5. The drag-and-drop instance is actually the dragrid.vue instance. It caches its instance into the cache in the created hook function. Here, the instance can be obtained from the cache according to the name, so that the instance can be called. method in the instance.
6.instance.current = dragNodeId; After setting, the styles of the dragdrop node and placeholder node are applied.
7. The offsetX and offsetY in the cached data are the offsets of the drag handle relative to the upper left corner of the node.
drag
After the dragging behavior occurs, the mouse move will execute this method, and the node will be moved by continuously updating the style of the dragged node.
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)'; }
Mainly calculates the offset of the node relative to the container: the distance between the mouse and the page - the offset of the container - the distance between the mouse and the node. The distance between the node and the container is the distance between the node and the container.
dragEnd
Mainly reset the state. The logic is relatively simple, so I won’t go into details.
At this point, a single node can be moved following the mouse.
Enable the placeholder to follow the dragging node movement
This section is about the placeholder node (the shadow part of the placeholder) moving together with the dragging node. The main idea is:
By dragging the offset of the node from the container (x, y in the drag method), it can be converted into the coordinates of the corresponding grid.
If the converted coordinates change, update the coordinates of the placeholder node.
The code added in the drag method is as follows:
// 坐标转换 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 rearrangement and upward movement
There are two core points in this section:
Use a two-dimensional array to represent the grid, so that the location information of the nodes can be marked in this two-dimensional array.
As long as a node changes in nodes, it must be rearranged and each node must be moved up as much as possible.
Construction of two-dimensional array
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如何实现单点登录跨域问题(详细教程)
The above is the detailed content of How to implement grid-layout function using vue. For more information, please follow other related articles on the PHP Chinese website!