vue grid layout 允许重叠(Vue Grid Layout -️ 适用Vue.js的栅格布局系统(保姆级使用教程))
目录
一 、 Vue Grid Layout 简介
二 、vue-grid-layout 的安装与使用
三、 属性
3.1 gridItem 的必须属性
3.2 框架元素的实际宽度高度计算方式
3.3 元素尺寸的计算方法总结
3.4 gridLayout 的属性
四 、事件
五 、vue-grid-layout 的实际应用
5.1 移动到任意位置
5.2 移动事件与调整大小
5.3 实现两个元素的交换
5.4 指定允许拖动的元素
5.5 动态添加/删除元素
5.6 外部添加元素
5.7 动态拖拽预览
六 、 Vue3 的使用
总结
兄弟们 ,有啥好的录屏gif工具推荐一下?
官网:Vue Grid Layout -️ 适用Vue.js的栅格布局系统
Gitee:https://gitee.com/wfeng0/vue2-grid-layout
一 、 Vue Grid Layout 简介
在官网的描述中 ,我们可以看出,该栅格布局具有以下特性:
在具有拖拽组成页面 、组件动态调整大小 、边缘碰撞监测的系统中 ,使用该布局无疑是最合适的 。当然 ,目前也有很多现成的文章 、博客 ,今天主要按照官网的学习思路 ,讲解一下该栅格系统的基本使用 。
该栅格系统目前对 vue2 的支持是最好的 ,vue3 是需要用插件支持的 ,会在小节详细讲解 。
二 、vue-grid-layout 的安装与使用
// 安装: npm install vue-grid-layout --save // 引用: import { GridLayout, GridItem } from vue-grid-layout // 注册: components:{ GridLayout, GridItem }完成上诉步骤之后 ,复制官网的《用法》一节代码到项目中:
data() { return { // 定义栅格系统数据源 layout: [ { "x": 0, "y": 0, "w": 2, "h": 2, "i": "0" }, { "x": 2, "y": 0, "w": 2, "h": 4, "i": "1" }, { "x": 4, "y": 0, "w": 2, "h": 5, "i": "2" }, { "x": 6, "y": 0, "w": 2, "h": 3, "i": "3" }, { "x": 8, "y": 0, "w": 2, "h": 3, "i": "4" }, { "x": 10, "y": 0, "w": 2, "h": 3, "i": "5" }, { "x": 0, "y": 5, "w": 2, "h": 5, "i": "6" }, { "x": 2, "y": 5, "w": 2, "h": 5, "i": "7" }, { "x": 4, "y": 5, "w": 2, "h": 5, "i": "8" }, { "x": 6, "y": 3, "w": 2, "h": 4, "i": "9" }, { "x": 8, "y": 4, "w": 2, "h": 4, "i": "10" }, { "x": 10, "y": 4, "w": 2, "h": 4, "i": "11" }, { "x": 0, "y": 10, "w": 2, "h": 5, "i": "12" }, { "x": 2, "y": 10, "w": 2, "h": 5, "i": "13" }, { "x": 4, "y": 8, "w": 2, "h": 4, "i": "14" }, { "x": 6, "y": 8, "w": 2, "h": 4, "i": "15" }, { "x": 8, "y": 10, "w": 2, "h": 5, "i": "16" }, { "x": 10, "y": 4, "w": 2, "h": 2, "i": "17" }, { "x": 0, "y": 9, "w": 2, "h": 3, "i": "18" }, { "x": 2, "y": 6, "w": 2, "h": 2, "i": "19" } ], } }, <grid-layout :layout.sync="layout" :col-num="12" :row-height="30" :is-draggable="true" :is-resizable="true" :is-mirrored="false" :vertical-compact="true" :margin="[10, 10]" :use-css-transforms="true" > <grid-item v-for="item in layout" :x="item.x" :y="item.y" :w="item.w" :h="item.h" :i="item.i" :key="item.i"> {{item.i}} </grid-item> </grid-layout>得到如下图 ,即表示该栅格系统已经能正常使用了 ,下面慢慢介绍其属性与事件方法 。
给 gridItem(是item) 添加样式:
<grid-item v-for="item in layout" :x="item.x" :y="item.y" :w="item.w" :h="item.h" :i="item.i" :key="item.i" class="gridItem"> {{ item.i }} </grid-item> // 对应的less <style lang="less" scoped> .gridItem { border: solid black 1px; background-color: #CCCCCC; } </style>这样子,是不是就很像官网的例子了
三 、 属性
简单说一下经常用到的属性 ,所有的属性可以前往官网查看描述,我们先来了解一下gridItem的必要属性吧 ,它对我们的讲解是非常重要的 。
3.1 gridItem 的必须属性
这是gridItem的数据项:{ "x": 0, "y": 0, "w": 2, "h": 2, "i": "0" },包含x、y 、w 、h、i 。
* 1. i: 栅格中元素的ID
* 2. x: 标识栅格元素位于第几列
* 3. y: 标识栅格元素位于第几行
* 4. w: 标识栅格元素的初始宽度(值为colWidth的倍数)
* 5. h: 标识栅格元素的初始高度(值为rowHeight的倍数)
理解基本概念后,详细说说参数含义(只取第一个数据项:{ "x": 0, "y": 0, "w": 2, "h": 2, "i": "0" }):
将X设为5:
将Y设为3:【目前无法设置为3 ,因为item会一直处于第一行 ,稍后再处理,因为现在还不能处于随意位置】
而W 、H就相对简单了 ,控制数据项的宽度 、高度:
* 6. minW:栅格元素的最小宽度(值为colWidth的倍数)
* 7. minH:栅格元素的最小高度(值为rowHeight的倍数)
* 8. maxW:栅格元素的最大宽度(值为colWidth的倍数)
* 9. maxH:栅格元素的最大高度(值为rowHeight的倍数)
了解了基本属性后 ,我们可以看GridLayout的属性了 ,因为item的其他属性会继承父级属性 ,在看其元素前 ,我们需要知道框架的元素计算方法 。
3.2 框架元素的实际宽度高度计算方式
图示:vue-grid-layout 的格子数示意
已知colNum是定义列数,rowHeight是指每一行的高度(这个的单位是 px); 如上图 ,将父盒子拆分为三行、五列 ,我放一个格子元素 ,实际占比是下图:
元素的实际高宽 ,等于一个格子的尺寸,问:占2×2个格子数的元素尺寸呢?
用代码验证一下:
就是需要理解margin的存在即可 。 因此可以得出结论
// 元素的尺寸计算方法 宽度 width = ( box / colNum ) * w + ( w - 1 ) * margin + border 高度 height= rowHeight * h +( h - 1 ) * margin + border我们用代码验证一下这个格子数:
grid-layout 下可以放多个grid-item ,我们用一个做背景框 ,一个做拖拽,背景框不可拖拽 、不可缩放:is-draggable="false" :is-resizable="false" ,注意顺序 ,放在第一个会在最下面,效果如下 。
这样更清晰看到 ,缩放后 ,元素的尺寸确实是包含margin值的。
元素的宽高与实际的w 、h值 ,是存在一个加上(n-1)的margin值的关系 , 花了很大篇幅说明了这个框架的宽高计算方式 ,一定记得是(n-1)的关系 ,在页面上计算元素尺寸时 ,别忘了margin的存在!
3.3 元素尺寸的计算方法总结
【3月17日 新增章节:元素尺寸的计算方法】
我看很多人的评论 ,还是对这个元素的尺寸计算有疑问 ,我现在再对这个难点讲解一下:
1. 我们的拖拽区 width: 1500px; height:650px;将其宽度分成10份,每一份多宽?(占多少px)
我们知道 ,配置时 ,我们设定了 margin:[10,10],因此 ,每一个元素 ,都应具有上下左右的边距 。因此,得出下图:
2. :row-height="50" 这个属性表示每一行的高度是 50px ,那么 ,650px的高度 ,可以分成多少行?
这里也是有 n+1 的间距 ,解不等式 ,就可得到 最大是10 。
我们总结一下计算公式:
宽度:设共可分为 n 列 ,那么 , width - (n+1)*margin / n = 每个元素的实际宽度
高度:设共可分为 m 行 ,那么 ,row-height * m + (m + 1) * margin <= height
因此,元素的宽高 ,可以根据实际的拖拽区的宽高 ,动态计算出来
不知道这么解释,能不能理解?如果还是有些模糊 ,可以看看我的下一篇项目实践 ,或者给我留言。
3.4 gridLayout 的属性
* 1. layout:栅格布局的数据源,数据源为数组Array ,数据项为对象 ,必须包含 i, x, y, w 和 h 属性.
* 2. colNum:定义栅格系统的列数
* 3. rowHeight:每行的高度 ,单位像素
* 4. maxRows:定义最大行数
* 5. isDraggable:标识栅格中的元素是否可拖拽
* 6. isResizable:标识栅格中的元素是否可调整大小
* 7. preventCollision:防止碰撞属性 ,值设置为ture时 ,栅格只能拖动至空白处
(我们将数据项宽度高度设为1 ,便于查看效果)
colNum:是定义列数
rowHeight:是指每一行的高度(这个的单位是 px)
margin:定义栅格中的元素边距(数组中第一个元素表示水平边距 ,第二个表示垂直边距 ,单位为像素)
其他属性就比较简单了 ,不再大篇幅介绍了,后面遇到了我们再细说 。主要大家一定要了解计算规则 ,这个框架就非常容易掌握了 ,遇到BUG,基本上是margin的问题 。
四 、事件
官网描述:事件 | Vue Grid Layout -️ 适用Vue.js的栅格布局系统
这章节官网已经描述得非常好了 ,也有demo 、样例 ,我就不细说了。
五 、vue-grid-layout 的实际应用
5.1 移动到任意位置
上诉还遗留一个问题没处理,就是元素不能移动到任意位置
gridLayout verticalCompact 属性: 标识布局是否垂直压缩,为false时 ,垂直方向不压缩 ,可以放置元素 。
5.2 移动事件与调整大小
事件分两组 ,事件执行中和事件执行完毕 。
移动事件有三个参数:i 、newX 、newY ,i标识唯一移动元素ID和新的位置
@move="moveEvent"
@moved="movedEvent"
调整大小事件有5个参数:i 、newH 、newW及h、w的px(像素)单位
@resize="resizeEvent"
@resized="resizedEvent"
常用的事件就是这两组 ,我们也会详细说明及应用 。
5.3 实现两个元素的交换
元素的交换是最简单的应用 ,当元素覆盖到一个元素上时 ,交换两者的位置 。类似交换两个数 ,一定需要第三个变量 ,我们使用深拷贝的方式实现数据缓存 。
mounted() { this.map = JSON.parse(JSON.stringify(this.layout)); },实现思路:移动中,判断位置上是否有元素 ,有的话 ,交换两者数据即可 。
moveEvent(i, x, y) { console.log("移动中--move", i, x, y); // 1 根据移动位置,循环数据源当前位置是否有元素,不包含自己的位置 const finditem = this.map.find( (item) => item.x === x && item.y === y && item.i != i ); if (!finditem) return; console.log("找到了", finditem); this.changePosition( this.layout.find((item) => item.i === i), finditem ); },逻辑理清后 ,开始交换了:
// 交换位置: changePosition(item1, item2) { console.log(item1, item2); // 定义中间变量交换 const temp = this.deepClone(item1); // 注意:修改的是影响显示的实际数组 layout this.layout.forEach((item) => { // 找到 item1,将 item1 的信息换成item2 if (item.i === item1.i) { item.x = item2.x; item.y = item2.y; } if (item.i === item2.i) { item.x = temp.x; item.y = temp.y; } }); // 实现交换后 ,及时同步 数据映射 this.map = this.deepClone(this.layout); },但是这样还有一个小问题,我希望是两个原始位置交换 ,而不是移动过程中的最新位置 ,这就需要获取数据映射中的原始数据:
实现效果:
简单实现到这就行了 ,当然还有其他的问题 ,比如相同尺寸的才能交换 、多换少 、匹配后非立马交换等问题 ,还是影响用户体验的 ,我们将这些问题留后面处理 。
5.4 指定允许拖动的元素
这个场景比较常见 ,在实际项目中 ,不可能整体结构允许拖动 ,因为要涉及结构的事件响应等,因此 ,为其设计允许拖动的元素就比较重要了 。
【GridItem 属性】
# dragIgnoreFrom(标识栅格元素中哪些子元素无法触发拖拽事件 ,值为css-like选择器)
# dragAllowFrom (标识栅格元素中哪些子元素可以触发拖拽事件,值为css-like选择器)
<grid-item v-for="item in layout" :x="item.x" :y="item.y" :w="item.w" :h="item.h" :i="item.i" :key="item.i" @move="moveEvent" @moved="movedEvent" @resize="resizeEvent" @resized="resizedEvent" class="gridItem" :style="{ backgroundColor: item.i === 0 ? : pink }" drag-ignore-from=".gridBox" drag-allow-from=".draggable" > <div class="gridBox">不允许拖动</div> <div class="draggable"></div> </grid-item> .gridItem { position: relative; display: flex; flex-direction: column; justify-content: center; align-items: center; border: solid #fff 1px; background-color: #cccccc; .draggable { position: absolute; left: 0; top: 0; width: 10px; height: 10px; background-color: #000; border-radius: 10px; cursor: pointer; } }只有指定元素才能拖动 ,其他未指定的均不允许拖动:
5.5 动态添加/删除元素
动态添加元素就是往数组对象中 push 新的item数据项:
add() { this.layout.push({ x: 1, y: 2, // puts it at the bottom w: 1, h: 1, i: this.layout.length + 1, }); },而删除元素 ,就是获取元素的i,执行 this.layout.splice(index, 1) 操作 。
dele(i) { this.layout.splice( this.layout.findIndex((item) => item.i === i), 1 ); },5.6 外部添加元素
原理就是利用事件源 ,将外部元素拖动到布局内 ,动态计算其位置 ,动态添加元素。
为了计算方便 ,我们将布局系统放置 top:0,left:0的位置
这里就需要计算我们的格子数、格子宽高 。
5.6.1 拖拽到布局内 ,会得到实时坐标
事件源中 ,有 x: 334 y: 161 ,两个属性.
5.6.2 动态计算释放后 ,该在什么位置
我们用draged事件监听 ,移动中事件触发太多次了(事件源是一样的,均有 x 、y属性) 。
当前坐标 336 299 ,我们如何确定位于那个格子中(需要格子尺寸做计算)?
按照我们上的计算方法:
// 元素的尺寸计算方法 宽度 width = ( box / colNum ) * w + ( w - 1 ) * margin + border 高度 height= rowHeight * h +( h - 1 ) * margin + border格子尺寸: ( box / colNum )* rowHeight,我就不算了 ,直接获取:
因此,每一个格子尺寸是 270*102 ,当前坐标 336 299 ,计算位置【位置指的是在栅格系统中的x 、y】:
需要处理 margin ,我们将其归为格子的尺寸 ,因此 ,计算时 ,格子是180*112
336/280=1.2
299/112=2.6
我们向上取整:应该在第三行、第二列。
能获取到坐标后动态push元素:
// 动态添加元素 this.layout.push({ x: gx - 1, y: gy - 1, w: 1, h: 1, i: `item(x:${ex},y:${ey})`, });5.7 动态拖拽预览
上面实现太生硬了 ,我们拖进去 ,不知道其具体位置 ,我们做一个预览功能.(新的grid-item)
拖动到某一个格子时 ,能实时展现预览位置能更好 。新建 grid-item,使用 previewData:[]空数组做数据源 ,拖拽事件执行过程中 ,动态添加preview的数据,实时更新预览数据 ,这样的交互是最好的:
六 、 Vue3 的使用
【vue3章节讲解太简单了 ,今天补充一下吧】
// 下载 vue-grid-layout npm install vue-grid-layout --save // 下载 vue3 插件支持 npm install vue-grid-layout@3.0.0-beta1 --save // main.js中注册 import gridLayout from vue-grid-layout createApp(App).use(gridLayout).mount(#app) // 在main.js注册后,不需要在 import 引入 ,直接使用(App.vue) <template> <grid-layout :layout="layout" :col-num="12" :row-height="30" :is-draggable="true" :is-resizable="true" :is-mirrored="false" :vertical-compact="true" :margin="[10, 10]" :use-css-transforms="true" > <grid-item v-for="item in layout" :x="item.x" :y="item.y" :w="item.w" :h="item.h" :i="item.i" :key="item.i" > {{ item.i }} </grid-item> </grid-layout> </template> <script setup> import { reactive } from "vue"; // 使用 vue3 setup 语法糖 const layout = reactive([ { x: 0, y: 0, w: 2, h: 2, i: "0" }, { x: 2, y: 0, w: 2, h: 4, i: "1" }, { x: 4, y: 0, w: 2, h: 5, i: "2" }, { x: 6, y: 0, w: 2, h: 3, i: "3" }, { x: 8, y: 0, w: 2, h: 3, i: "4" }, { x: 10, y: 0, w: 2, h: 3, i: "5" }, { x: 0, y: 5, w: 2, h: 5, i: "6" }, { x: 2, y: 5, w: 2, h: 5, i: "7" }, { x: 4, y: 5, w: 2, h: 5, i: "8" }, { x: 6, y: 3, w: 2, h: 4, i: "9" }, { x: 8, y: 4, w: 2, h: 4, i: "10" }, { x: 10, y: 4, w: 2, h: 4, i: "11" }, { x: 0, y: 10, w: 2, h: 5, i: "12" }, { x: 2, y: 10, w: 2, h: 5, i: "13" }, { x: 4, y: 8, w: 2, h: 4, i: "14" }, { x: 6, y: 8, w: 2, h: 4, i: "15" }, { x: 8, y: 10, w: 2, h: 5, i: "16" }, { x: 10, y: 4, w: 2, h: 2, i: "17" }, { x: 0, y: 9, w: 2, h: 3, i: "18" }, { x: 2, y: 6, w: 2, h: 2, i: "19" }, ]); </script> <style> * { margin: 0; padding: 0; box-sizing: border-box; } </style>添加样式:
.grid-item { display: flex; align-items: center; justify-content: center; border: solid #ccc 1px; background-color: #ccc; }得到如下:
那剩下的就是事件啥的了 ,跟vue2 是一样的 。主要是能在vue3中出现这个图,表示已经没问题了。
总结
栅格布局难度不大 ,但是要理解里面的原理 ,并实际应用到自己的项目开发 ,还要结合自己项目的实际情况 。里面涉及的逻辑主要是计算宽高 、拖拽事件处理等多角度问题 ,想要实现更加丰富的功能 ,主要是能使用多个 item 系统 ,实现堆叠效果 。优化部分留给大家思考了 ,有问题欢迎交流讨论 。别忘了推荐些gif工具哈~
创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!