使用vue实现grid-layout功能实例代码


Posted in Javascript onJanuary 05, 2018

1.先clone项目到本地。

2.git reset --hard commit 命令可以使当前head指向某个commit。

完成html的基本布局

点击复制按钮来复制整个commit id。然后在项目根路径下运行 git reset 。用浏览器打开index.html来预览效果,该插件的html主要结果如下:

<!-- 节点容器 -->
<div class="dragrid">
 <!-- 可拖拽的节点,使用translate控制位移 -->
 <div class="dragrid-item" style="transform: translate(0px, 0px)">
 <!-- 通过slot可以插入动态内容 -->
 <div class="dragrid-item-content">
  
 </div>
 <!-- 拖拽句柄 -->
 <div class="dragrid-drag-bar"></div>
 <!-- 缩放句柄 -->
 <div class="dragrid-resize-bar"></div>
 </div>
</div>

使用vue完成nodes简单排版

先切换commit,安装需要的包,运行如下命令:

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

这一步一个是搭建环境,这个直接看webpack.config.js配置文件就可以了。

另一个就是节点的排版(layout),主要思路是把节点容器看成一个网格,每个节点就可以通过横坐标(x)和纵坐标(y)来控制节点的位置,左上角坐标为(0, 0);通过宽(w)和高(h)来控制节点大小;每个节点还必须有一个唯一的id。这样节点node的数据结构就为:

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

其中cellW、cellH为每个格子的宽和高,这样计算节点的宽和高及位移就很容易了。

完成单个节点的拖拽

拖拽事件

1.使用mousedown、mousemove、mouseup来实现拖拽。

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

执行流程大致如下:

鼠标在拖拽句柄上按下, onMouseDown 方法触发,在eventHandler中存储一些值之后,鼠标移动则触发 onMouseMove 方法,第一次进入时 eventHandler.drag 为false,其中isDrag方法会根据位移来判断是否是拖拽行为(横向或纵向移动5像素),如果是拖拽行为,则将drag属性设置为true,同时执行 dragdrop.dragStart 方法(一次拖拽行为只会执行一次),之后鼠标继续移动,则就开始执行 dragdrop.drag 方法了。最后鼠标松开后,会执行 onMouseUp 方法,将一些状态重置回初始状态,同时执行 dragdrop.dragEnd 方法。

拖拽节点

拖拽节点的逻辑都封装在dragdrop.js这个文件里,主要方法为 dragStart 、 drag 、 dragEnd 。

dragStart

在一次拖拽行为中,该方法只执行一次,因此适合做一些初始化工作,此时代码如下:

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.参数el为拖拽句柄元素,offsetX为鼠标距离拖拽句柄的横向偏移,offsetY为鼠标距离拖拽句柄的纵向偏移。

2.通过el可以向上递归查找到拖拽节点(dragNode),及拖拽容器(dragContainer)。

3.dragdrop元素是真正鼠标控制拖拽的节点,同时与之对应的布局节点会变为占位节点(placeholder),视觉上显示为阴影效果。

4.设置拖拽节点其实就将点击的dragNode的innerHTML设置到dragdrop中,同时将样式也应用过去。

5.拖拽实例,其实就是dragrid.vue实例,它在created钩子函数中将其实例缓存到cache中,在这里根据name就可以从cache中得到该实例,从而可以调用该实例中的方法了。

6.instance.current = dragNodeId; 设置之后,dragdrop节点及placeholder节点的样式就应用了。

7.缓存数据中的offsetX、offsetY是拖拽句柄相对于节点左上角的偏移。

drag

发生拖拽行为之后,鼠标move都会执行该方法,通过不断更新拖拽节点的样式来是节点发生移动效果。

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

主要是计算节点相对于容器的偏移:鼠标距离页面距离-容器偏移-鼠标距离拽节点距离就为节点距离容器的距离。

dragEnd

主要是重置状态。逻辑比较简单,就不再细说了。

到这里已经单个节点已经可以跟随鼠标进行移动了。

使placeholder可以跟随拖拽节点运动

本节是要讲占位节点(placeholder阴影部分)跟随拖拽节点一起移动。主要思路是:

通过拖拽节点距离容器的偏移(drag方法中的x, y),可以将其转化为对应网格的坐标。

转化后的坐标如果发生变化,则更新占位节点的坐标。

drag方法中增加的代码如下:

// 坐标转换
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重排及上移

本节核心点有两个:

用一个二维数组来表示网格,这样节点的位置信息就可以在此二维数组中标记出来了。

nodes中只要某个节点发生变化,就要重新排版,要将每个节点尽可能地上移。

二维数组的构建

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. 拖拽控制滚动条。
  3. 拖拽边界的限制。
  4. 向下拖拽,达到碰撞节点1/2高度就发生换位。

总结

以上所述是小编给大家介绍的使用vue实现grid-layout功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
Valerio 发布了 Mootools
Sep 23 Javascript
Jquery跨域获得Json时invalid label错误的解决办法
Jan 11 Javascript
extjs 3.31 TreeGrid实现静态页面加载json到TreeGrid里面
Apr 02 Javascript
Javascript玩转继承(一)
May 08 Javascript
jQuery获取动态生成的元素示例
Jun 15 Javascript
jQuery中on()方法用法实例
Jan 19 Javascript
JavaScript父子窗体间的调用方法
Mar 31 Javascript
安装使用Mongoose配合Node.js操作MongoDB的基础教程
Mar 01 Javascript
jQuery EasyUI提交表单验证
Jul 19 Javascript
JS+HTML5 FileReader对象用法示例
Apr 07 Javascript
Angular如何引入第三方库的方法详解
Jul 13 Javascript
编写v-for循环的技巧汇总
Dec 01 Javascript
详解为Bootstrap Modal添加拖拽的方法
Jan 05 #Javascript
JS交互点击WKWebView中的图片实现预览效果
Jan 05 #Javascript
Vue组件的使用教程详解
Jan 05 #Javascript
基于three.js编写的一个项目类示例代码
Jan 05 #Javascript
js中this对象用法分析
Jan 05 #Javascript
Node中使用ES6语法的基础教程
Jan 05 #Javascript
解决JSON.stringify()自动将中文转译成unicode的问题
Jan 05 #Javascript
You might like
各种战术和打法的原创者
2020/03/04 星际争霸
CMSPRESS 10行代码搞定 PHP无限级分类2
2018/03/30 PHP
jQuery Jcrop插件实现图片选取功能
2011/11/23 Javascript
js调用图片隐藏&amp;显示实现代码
2013/09/13 Javascript
在百度知道团队中快速审批新成员的js脚本
2014/02/02 Javascript
js统计页面的来访次数实现代码
2014/05/09 Javascript
jQuery寻找n以内完全数的方法
2015/06/24 Javascript
JS基于ocanvas插件实现的简单画板效果代码(附demo源码下载)
2016/04/05 Javascript
JS自定义函数对web前端上传的文件进行类型大小判断
2016/10/19 Javascript
vuejs通过filterBy、orderBy实现搜索筛选、降序排序数据
2020/10/26 Javascript
Angular-Ui-Router+ocLazyLoad动态加载脚本示例
2017/03/02 Javascript
js实现华丽的九九乘法表效果
2017/03/29 Javascript
vue 的keep-alive缓存功能的实现
2018/03/22 Javascript
vue 标签属性数据绑定和拼接的实现方法
2018/05/17 Javascript
jQuery实现的监听导航滚动置顶状态功能示例
2018/07/23 jQuery
Vue前后端不同端口的实现方法
2018/09/19 Javascript
详解jQuery-each()方法
2019/03/13 jQuery
小程序实现图片预览裁剪插件
2019/11/22 Javascript
Python的加密模块md5、sha、crypt使用实例
2014/09/28 Python
python字典操作实例详解
2017/11/16 Python
python代码实现ID3决策树算法
2017/12/20 Python
对Python使用mfcc的两种方式详解
2019/01/09 Python
python实现年会抽奖程序
2019/01/22 Python
用python打印1~20的整数实例讲解
2019/07/01 Python
python调用并链接MATLAB脚本详解
2019/07/05 Python
python3 selenium自动化测试 强大的CSS定位方法
2019/08/23 Python
Python使用urllib模块对URL网址中的中文编码与解码实例详解
2020/02/18 Python
python用分数表示矩阵的方法实例
2021/01/11 Python
CSS3中的@keyframes关键帧动画的选择器绑定
2016/06/13 HTML / CSS
新西兰领先的内衣店:Bendon Lingerie新西兰
2018/07/11 全球购物
实习医生自我评价
2013/09/22 职场文书
抄作业检讨书
2014/02/17 职场文书
《将心比心》教学反思
2014/04/08 职场文书
岗位明星事迹材料
2014/05/18 职场文书
谢师宴家长答谢词
2015/09/30 职场文书
浅谈mysql哪些情况会导致索引失效
2021/11/20 MySQL