vue-grid-layout是一个vue栅格拖动布局的组件.官网
栅格布局,即网格布局,是一种新的 CSS
布局模型,比较擅长将一个页面划分为几个主要区域,以及定义这些区域的大小、位置、层次等关系。
与flex布局的区别:
- Flexbox 是一维布局系统,适合做局部布局,比如导航栏组件。
- 网格布局是二维布局系统,可以更好的操作行和列,通常用于整个页面的规划。
- 虽然 Flexbox 也可以用于大的页面布局,但是没有 网格布局强大和灵活。二者结合使用更加轻松。二者从应用场景来说并不冲突。
- flex 布局一次只能处理一个维度上的元素布局,一行或者一列(flex-direction)。网格布局是将容器划分成了“行”和“列”,产生了一个个的网格,我们可以将网格元素放在与这些行和列相关的位置上,从而达到我们布局的目的。
vue-grid-layout优点:
元素可拖动
元素可调整大小
边界检查拖动和调整大小
可以添加或删除窗口小部件而无需重建网格
布局可以序列化
响应式布局
属性 GridLayout常用参数:
参数 | 含义 | 数据类型 |
---|---|---|
layout | 栅格的初始布局 | Array |
colNum | 栅格系统的列数 | Number 默认 12 |
rowHeight | 每行的高度 | Number 默认是单位是 px |
isDraggable | 栅格中的元素是否可拖拽 | Boolean |
isResizable | 是否可以改变大小 | Boolean |
margin | 栅格中的元素边距 | Array |
属性GridItem参数:
参数 | 含义 | 类型 |
---|---|---|
i | id | 类型不限 |
x | 格元素位于第几列 | Number |
y | 元素位于第几行 | Number |
w | 占几块(元素的初始宽度,值为colWidth 的倍数) | Number |
h | 元素的初始高度,值为rowHeight 的倍数。 | Number |
isDraggable | 元素是否可拖拽 | Boolean |
isResizable | 元素是否可以改变大小 | Boolean |
static | 是否为静态的(无法拖拽、调整大小或被其他元素移动) | Boolean |
事件:
每一个栅格元素grid-item上都可以添加监听器,用于监听移动和调整大小事件,这样父级Vue对象就可以收到通知。
GridLayout:
事件 | 含义 |
layoutCreatedEvent | 对应Vue生命周期的created |
layoutBeforeMountEvent | 对应Vue生命周期的beforeMount |
layoutMountedEvent | 对应Vue生命周期的mounted |
layoutReadyEvent | 当完成mount中的所有操作时生成的事件 |
layoutUpdatedEvent | 布局updated事件 |
GridItem:
事件 | 含义 |
moveRvent | 移动时的事件 |
resizeRvent | 调整大小时的事件 |
mocedRvent | 移动后的事件 |
resizedRvent | 调整大小后的事件 |
containerResizedRvent | 栅格元素/栅格容器更改大小的事件(浏览器窗口或其他) |
示例:
<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">
<span class="text">{{item.i}}</span>
<span class="remove" @click="removeItem(item.i)">x</span>
</grid-item>
</grid-layout>
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"},
],
源码解析:
1、栅格布局实现逻辑:
往grid-item上面放style样式,当标识是否使用CSS属性 useCssTransforms为true时,则使用
transform来定位;(默认为true)
export function setTransform(top, left, width, height): Object {
// Replace unitless items with px
const translate = 'translate3d(' + left + 'px,' + top + 'px, 0)'
return {
transform: translate,
WebkitTransform: translate,
MozTransform: translate,
msTransform: translate,
OTransform: translate,
width: width + 'px',
height: height + 'px',
position: 'absolute',
}
}
否则使用position定位,设置子元素的top、 left值;
export function setTopLeft(top, left, width, height): Object {
return {
top: top + 'px',
left: left + 'px',
width: width + 'px',
height: height + 'px',
position: 'absolute',
}
}
2、拖拽、缩放等交互;
使用的前端拖拽插件interact.js,提供拖,放,调整尺寸和多点触摸手势功能。
grid-item 通过interact的on方法,监听拖放、调整尺寸等事件。
//将当前拖动元素经过interact实例化之后,即可监听相应的事件
//拖拽事件
this.interactObj.on('dragstart dragmove dragend', function (event) {
self.handleDrag(event)
})
//缩放事件
this.interactObj.on('resizestart resizemove resizeend',function (event) {
self.handleResize(event)
})
//元素拖拽时
handleDrag(event) {
//如果禁止拖拽、调整大小则reture
if (this.static) return
if (this.isResizing) return
const position = getControlPosition(event) // 从事件中获取当前拖动点。这是用作偏移量的。
// Get the current drag point from the event. This is used as the offset.
if (position === null) return // not possible but satisfies flow
const { x, y } = position
// let shouldUpdate = false;
let newPosition = { top: 0, left: 0 }
//通过监听拖拽事件,将拖拽的位置赋值给newPosition
switch (event.type) {
case 'dragstart': {
this.previousX = this.innerX
this.previousY = this.innerY
//元素的父级视图定位
let parentRect =event.target.offsetParent.getBoundingClientRect()//offsetParent为离自身最近且经过定位的父元素
//元素的视图定位
let clientRect = event.target.getBoundingClientRect()
//是否为镜像反转
//计算出当前模块left与top的距离
if (this.renderRtl) {
newPosition.left = (clientRect.right - parentRect.right) * -1
} else {
newPosition.left = clientRect.left - parentRect.left
}
newPosition.top = clientRect.top - parentRect.top
this.dragging = newPosition
console.log('dragstart====', this.dragging)
this.isDragging = true
break
}
case 'dragend': {
if (!this.isDragging) return
let parentRect = event.target.offsetParent.getBoundingClientRect()
let clientRect = event.target.getBoundingClientRect()
// Add rtl support
if (this.renderRtl) {
newPosition.left = (clientRect.right - parentRect.right) * -1
} else {
newPosition.left = clientRect.left - parentRect.left
}
newPosition.top = clientRect.top - parentRect.top
// console.log("### drag end => " + JSON.stringify(newPosition));
// console.log("### DROP: " + JSON.stringify(newPosition));
this.dragging = null
console.log('dragend====', this.dragging)
this.isDragging = false
// shouldUpdate = true;
break
}
case 'dragmove': {
const coreEvent = createCoreData(this.lastX, this.lastY, x, y)
// Add rtl support
if (this.renderRtl) {
newPosition.left = this.dragging.left - coreEvent.deltaX
} else {
newPosition.left = this.dragging.left + coreEvent.deltaX
}
newPosition.top = this.dragging.top + coreEvent.deltaY
this.dragging = newPosition
console.log('dragmove====', this.dragging)
break
}
}
// Get new XY
//根据left与top的计算出当前模块在XY的值(通过四舍五入的方式)
let pos
if (this.renderRtl) {
pos = this.calcXY(newPosition.top, newPosition.left)
} else {
pos = this.calcXY(newPosition.top, newPosition.left)
}
this.lastX = x
this.lastY = y
//仅用来打印
if (this.innerX !== pos.x || this.innerY !== pos.y) {
this.$emit('move', this.i, pos.x, pos.y)
}
if (
event.type === 'dragend' &&
(this.previousX !== this.innerX || this.previousY !== this.innerY)
) {
this.$emit('moved', this.i, pos.x, pos.y)
}
//将拖动时的位置数据传给容器,容器对layout与占位空间赋值
this.eventBus.$emit(
'dragEvent',
event.type,
this.i,
pos.x,
pos.y,
this.innerH,
this.innerW
)
},
// 拖渲染页面
dragEvent: function (eventName, id, x, y, h, w) {
//获取到当前的拖动的元素
let l = getLayoutItem(this.layout, id)
//GetLayoutItem sometimes returns null object
if (l === undefined || l === null) {
l = { x: 0, y: 0 }
}
//当拖动为dragmove、dragstart时,赋值占位符的位置
if (eventName === 'dragmove' || eventName === 'dragstart') {
this.placeholder.i = id
this.placeholder.x = l.x
this.placeholder.y = l.y
this.placeholder.w = w
this.placeholder.h = h
this.$nextTick(function () {
this.isDragging = true
})
//this.$broadcast("updateWidth", this.width);
this.eventBus.$emit('updateWidth', this.width)
} else {
this.$nextTick(function () {
this.isDragging = false
})
}
// Move the element to the dragged location.
//获取到其他元素的级联动作,将元素移动到拖动位置。
this.layout = moveElement(
this.layout,
l,
x,
y,
true,
this.preventCollision//防止碰撞属性
)
compact(this.layout, this.verticalCompact)
// needed because vue can't detect changes on array element properties
//让vue检测到数组元素属性的变化
this.eventBus.$emit('compact')
this.updateHeight()
if (eventName === 'dragend') this.$emit('layout-updated', this.layout)
},
相关问题:
- 标识栅格元素是否可拖拽(isDraggable)该属性只能将元素向下排,并不能形成左右替换的形式,并且在该组件中也没有元素互相替换位置这种属性;
- GridItem的XY参数,只能为整数;
- 没有下边界,发生碰撞的时候块会无限的往下移动;
总结:
视图页面中每一个面板都可拖拽,可随意放大放小,体验还是不错的。