-
前情提要
项目中需要实现视频分屏操作,除了传统的1、4、9……均匀分布,还需要支持用户自定义布局。这就类似excel的合并、拆分单元格功能。
-
思路
首先,选择布局模式。在尝试了css的flex、grid布局之后,还是不得不选择table。
其次,基于vue是数据驱动页面显示,所以不同于传统js的需要操作dom元素(这将提高代码量和难度),主要是需要定义一些关键参数来表示单元格之间的关系以及操作过程中的中间参数。
最后,计算单元格的跨行跨列值,这里参考了
主要是通过计算选中的区域的x、y值来计算合并的colspan和rowspan。
主要代码
methods: {
handleSplit() {
let item = this.isChosenTds[0];
let { rowIndex, colIndex, colspan, rowspan } = item;
for (let i = rowIndex; i < rowIndex + rowspan; i++) {
let new_colspan = [];
let new_rowspan = [];
let new_isChosen = [];
for (let j = 0; j < this.num; j++) {
new_colspan.push(1);
new_rowspan.push(1);
let isChosen =
j < colIndex || j > colIndex + colspan - 1 ? false : true;
new_isChosen.push(isChosen);
}
let obj = {
colspan: new_colspan,
rowspan: new_rowspan,
isChosen: new_isChosen
};
this.$set(this.layoutTable, i, obj);
this.$nextTick(() => {
this.selectTd();
});
},
handleMerge() {
let len = this.isChosenTds.length;
let newArr = [];
let a = 400 / this.num;
for (var i = 0; i < this.isChosenTds.length; i++) {
var td = this.isChosenTds[i];
let obj = {};
let rowspan = 0;
let colspan = 0;
if (td.pos.x == this.area.xMin && td.pos.y == this.area.yMin) {
rowspan = Math.round((this.area.yMax - this.area.yMin) / a);
colspan = Math.round((this.area.xMax - this.area.xMin) / a);
}
obj = { ...this.isChosenTds[i], rowspan, colspan };
newArr.push(obj);
}
this.layoutTable = this.createNewLayout(newArr);
this.$nextTick(() => {
this.selectTd();
});
},
createNewLayout(newArr) {
for (let i = 0; i < newArr.length; i++) {
let { colspan, rowspan, rowIndex, colIndex } = newArr[i];
let obj = this.layoutTable[rowIndex];
obj["colspan"][colIndex] = colspan;
obj["rowspan"][colIndex] = rowspan;
obj["isChosen"][colIndex] = colspan > 0 ? true : false;
}
let arr = this.layoutTable.map(item => {
let { colspan, rowspan, isChosen } = item;
if (!colspan) {
return {};
}
let new_colspan = [];
let new_rowspan = [];
let new_isChosen = [];
for (let i = 0; i < colspan.length; i++) {
if (colspan[i] !== 0) {
new_colspan.push(colspan[i]);
new_rowspan.push(rowspan[i]);
new_isChosen.push(isChosen[i]);
}
}
if (new_colspan.length > 0) {
return {
colspan: new_colspan,
rowspan: new_rowspan,
isChosen: new_isChosen
};
}
return {};
});
return arr;
},
selectTd() {
let table_tr = this.$refs.table_tr;
let arr = [];
for (let i = 0; i < this.layoutTable.length; i++) {
let tr = table_tr[i];
let { isChosen, colspan, rowspan } = this.layoutTable[i];
if (!isChosen) {
continue;
}
for (let j = 0; j < isChosen.length; j++) {
let td = tr.children[j];
if (isChosen[j]) {
arr.push({
rowIndex: i,
colIndex: j,
colspan: colspan[j],
rowspan: rowspan[j],
pos: {
x: td.offsetLeft,
y: td.offsetTop
}
});
}
}
}
this.isChosenTds = arr;
this.area = this.getAreaByTds();
},
getAreaByTds() {
let a = 400 / this.num;
let containTds = this.isChosenTds;
var area = { xMin: 99999, yMin: 99999, xMax: -1, yMax: -1 };
for (var i = 0; i < containTds.length; i++) {
var xMin = Number(containTds[i].pos.x);
var yMin = Number(containTds[i].pos.y);
var xMax = Number(containTds[i].pos.x + containTds[i].colspan * a);
var yMax = Number(containTds[i].pos.y + containTds[i].rowspan * a);
area.xMin = Math.min(area.xMin, xMin);
area.yMin = Math.min(area.yMin, yMin);
area.xMax = Math.max(area.xMax, xMax);
area.yMax = Math.max(area.yMax, yMax);
}
return area;
}
};
-
小结
个人认为难点在于合并,也就是跨行跨列的计算,拆分则比较简单,只需要生成标准表格即可。接着就是数据格式的定义和处理,我用了很多for循环,感觉代码不是很优雅。最后,代码还缺少类似参考博文中的能否进行合并拆分的判断以及鼠标移动选择的交互,整体上还可以继续优化的部分很多。至于布局的选择,table是最先想到的,但是由于个人不喜欢这个布局,所以中间走了很多弯路。事实证明不能任性。