vue-grid-layout是一款栅格拖拽布局的vue组件,但在添加新卡片时不会自动放到剩余空位置,因为新卡片的x、y初始值都默认为0,导致新卡片与原有卡片重叠,影响体验;
于是想要实现在添加新卡片时,在画布上自动计算出可放的位置坐标,前面有空位足够放下则放在前面,不够位置放到最后一行的行头;
实现算法之前先给出vue-grid-layout的大概用法,顺便算法也一起写在方法里:
template部分
<template>
<grid-layout
:layout.sync="layoutData"
:col-num="colNum"
:row-height="rowHeight"
:is-draggable="true"
:is-resizable="true"
:is-mirrored="false"
:vertical-compact="true"
:prevent-collision="false"
:margin="[10, 10]"
:use-css-transforms="false"
@layout-mounted="layoutMounted"
@layout-updated="layoutUpdated"
>
<grid-item
v-for="item in layoutData"
:x="item.x"
:y="item.y"
:w="item.w"
:h="item.h"
:i="item.i"
:key="item.i"
@resize="resizeEvent"
@resized="resizedEvent"
>
<component :is="item.name" />
</grid-item>
</grid-layout>
</template>
javascript部分
import VueGridLayout from 'vue-grid-layout';
import a from './components/a.vue'
import b from './components/b.vue'
export default{
name:'vueGridLayout',
components:{
GridLayout: VueGridLayout.GridLayout,
GridItem: VueGridLayout.GridItem,
a,
b
},
data(){
return {
layoutData:[{i: 0, x: 0, y: 0, w: 36, h: 28}, {i: 1, x: 36, y: 0, w: 36, h: 28}],
colNum: 50,
rowHeight: 10
}
},
methods:{
addNewItem( item ){
item.i = this.layoutData.length//i的值可以根据自己实际情况设置
const { x, y } = this.getPositionForNewItem( item )
item.x = x
item.y = y
//插入到画布
this.layoutData.push( item )
//到此完结
},
//给新添加的卡片找到合适的位置
getPositionForNewItem( newItem ){
//1、确定边界
let gridMap = [], maxX = this.colNum, maxY = 0
//2、找到Y的最大值
let allY = this.layoutData.map(v=>(item.y + item.h))
maxY = allY.length?Math.max.apply(null, allY) : 0
let isFind = false, findx = 0, findy = 0
if( maxY ){
//3、生成整个画布的方格地图
let i, j;
for(i = 0; i < maxX; i++){
gridMap[i] = []
for(j = 0; j < maxY; j++){
gridMap[i][j] = 0
}
}
//4、标注已被占用的方格
let a, b, axw, byh;
this.layoutData.forEach(({ x, y, w, h })=>{
axw = x + w; byh = y + h;
for(a = x; a < axw; a++){
for(b = y; b < byh; b++){
gridMap[a][b] = w
}
}
})
//5、根据以上标注的位置信息寻位,w表示已被占用,0表示未被占用
let x, y, cx, cy, xw, yh;
for(y = 0; y < maxY; y++){
yh = y + newItem.h
for(x = 0; x < maxX; x++){
if(gridMap[x] && gridMap[x][y]){
x = Math.min(maxX - 1, x + gridMap[x][y])
}
xw = x + newItem.w
if(!gridMap[x][y] && xw < maxX && yh < maxY){
//在画布内未被占用,则从这个位置开始寻位,看是否能找到足够的位置放新卡片
let isMatch = true
for(cy = y; cy < yh; c++){
for(cx = x; cx < xw; r++){
if(gridMap[cx][cy]){
x = cx + gridMap[cx][cy]//跳过组件的x,提升性能的关键一步
isMatch = false//进来说明已被占用,跳过x,y位置,找下一个方格
break
}
}
//被占用则退出当前寻位循环
if( !isMatch ){
break
}
}
//寻位结束:寻位的所有方格都没被占用的,则找到位置
if( isMatch ){
isFind = true
findx = x
findy = y
break
}
}
}
//找到则退出整个循环
if( isFind ){
break
}
}
//遍历完没有找到合适位置,则直接放在最后一行的开始位置x=0,y = maxY+1
if( !isFind ){
findx = 0
findy = maxY + 1
}
}
gridMap = []
allY = []
return { x: findx, y: findy }
}
}
}
好了,整个代码都写完了,然而我忽然有点明白为什么vue-grid-layout官方不自己来实现这个这么好用的功能了,其实还是考虑到性能问题,看看上面的代码写了多少个循环就知道,组件数量稍微多一点,操作肯定会卡,所以虽然实现了这个功能,但是用不用就要看实际情况了,或者大家看看有没有比这个更好的实现方法?进群告诉我一下?实践过程中解决这个问题可以通过Worker开启新线程进行“异步”计算,防止同步大计算量的等待时间过长或者页面卡顿,有关Worker的用法后续会写新的篇章。
好了,今天就到这,快去试试吧