在使用vue-grid-layout栅格布局组件的过程中,设置好的布局,要修改纵向外边距margin[1]时,发现每个组件的高度变化很大,但是修改横向外边距margin[0]时,组件的宽度却基本不变化,研究了很久差点没看明白官方计算高度、位置的思路,来看一下官方对组件新位置和新宽高的计算方法:
calcPosition: function (x, y, w, h) {
const colWidth = this.calcColWidth();
// add rtl support
let out;
if (this.renderRtl) {
out = {
right: Math.round(colWidth * x + (x + 1) * this.margin[0]),
top: Math.round(this.rowHeight * y + (y + 1) * this.margin[1]),
// 0 * Infinity === NaN, which causes problems with resize constriants;
// Fix this if it occurs.
// Note we do it here rather than later because Math.round(Infinity) causes deopt
width: w === Infinity ? w : Math.round(colWidth * w + Math.max(0, w - 1) * this.margin[0]),
height: h === Infinity ? h : Math.round(this.rowHeight * h + Math.max(0, h - 1) * this.margin[1])
};
} else {
out = {
left: Math.round(colWidth * x + (x + 1) * this.margin[0]),
top: Math.round(this.rowHeight * y + (y + 1) * this.margin[1]),
// 0 * Infinity === NaN, which causes problems with resize constriants;
// Fix this if it occurs.
// Note we do it here rather than later because Math.round(Infinity) causes deopt
width: w === Infinity ? w : Math.round(colWidth * w + Math.max(0, w - 1) * this.margin[0]),
height: h === Infinity ? h : Math.round(this.rowHeight * h + Math.max(0, h - 1) * this.margin[1])
};
}
return out;
}
可以看到,这里通过margin外边距的值计算组件的top、height
一步步来,首先,把计算height值的那一行后半部分拿出来分析下:
height: Math.round(this.rowHeight * h + Math.max(0, h - 1) * this.margin[1])
分割开来看:
this.rowHeight * h
:是行高乘以h高度,计算的是组件高度的实际像素值h-1
:也看不明白Math.max(0, h - 1)
:就是这里最小值是0Math.max(0, h - 1) * this.margin[1]
:这里看不懂了,乘以外边距是什么意思?
从最后一段代码可以看到组件高度height变化大的原因,margin[1]从1px增加到10px,高度基本增加了10倍左右了
接下来,将这段逻辑写成式子:设r是行高,h是组件的格子高度值,margin为m,列成公式如下:
r x h + (h - 1) x m
展开,去掉乘号,整理下:
rh + mh - m
//合并h
(r+m)h - m
上面的公式,假如将r+m看作新的行高,就能看出点思路了吧;
我们再来看top的计算公式:
Math.round(this.rowHeight * y + (y + 1) * this.margin[1])
同样,按上面的方法整理后,得到的公式是:
r x y + m x y + m
//合并y去掉乘号
(r+m)y + m
可以看到,同样是行高发生了变化,从r增加到r+m,后面多加的m就是纵外边距了;
综合两条式子一起看:
//height
(r+m)h - m
//top
(r+m)y + m
可以看到,就是行高发生了变化,top这里加多一个纵外边距m,在高度那里减少一个纵外边距m;
上面的公式导致m就算改变很小,整个画布的高度以及组件的高度都发生巨大的变化,这样是有点问题的;
从另一个方向看,我们改变横外边距时,却发现组件的宽度不会发生突变,且画布总宽度也不变,于是小编尝试打印了一下上述calcPosition方法中列宽colWidth的值,发现横向外边距margin[0]每增加1px,colwidth就减少1px,到这我立刻明白了,原来列宽是不断变小的,变化的值刚好是横向外边距margin[0]的值;
既然这样,那行高是不是也可以用同样的方法呢,所以我们将计算的top和height的那几段代码稍微修改下,最后方法如下(请注意top和height前后计算的变化):
calcPosition: function (x, y, w, h) {
const colWidth = this.calcColWidth();
// add rtl support
let out;
if (this.renderRtl) {
out = {
right: Math.round(colWidth * x + (x + 1) * this.margin[0]),
top: Math.round((this.rowHeight - this.margin[1]) * y + (y + 1) * this.margin[1]),
// 0 * Infinity === NaN, which causes problems with resize constriants;
// Fix this if it occurs.
// Note we do it here rather than later because Math.round(Infinity) causes deopt
width: w === Infinity ? w : Math.round(colWidth * w + Math.max(0, w - 1) * this.margin[0]),
height: h === Infinity ? h : Math.round((this.rowHeight - this.margin[1]) * h + Math.max(0, h - 1) * this.margin[1])
};
} else {
out = {
left: Math.round(colWidth * x + (x + 1) * this.margin[0]),
top: Math.round((this.rowHeight - this.margin[1]) * y + (y + 1) * this.margin[1]),
// 0 * Infinity === NaN, which causes problems with resize constriants;
// Fix this if it occurs.
// Note we do it here rather than later because Math.round(Infinity) causes deopt
width: w === Infinity ? w : Math.round(colWidth * w + Math.max(0, w - 1) * this.margin[0]),
height: h === Infinity ? h : Math.round((this.rowHeight - this.margin[1]) * h + Math.max(0, h - 1) * this.margin[1])
};
}
return out;
}
可以看到,其实就是将原来的行高this.rowHeight
换成了this.rowHeight - this..margin[1]
,这样改一下之后,随意改变外边距margin横纵向的值,组件和画布的高度都不会突变了;
其实可以看到height改了之后的代码:
height:Math.round((this.rowHeight - this.margin[1]) * h + Math.max(0, h - 1) * this.margin[1])
简化之后变成:
(r - m) x h + (h - 1) x m
rh - mh + mh - m
rh - m
同样,top的计算也一样:
top: Math.round((this.rowHeight - this.margin[1]) * y + (y + 1) * this.margin[1])
简化之后变成:
(r - m) x y + (y + 1) x m
ry - my + my + m
rh + m
其实这样看就很明显了,这样改之后,外边距增加则组件自身高度减少,这样总高度是没有变化的,只是top那边增加了一个margin[1],所以画布高度就不会突变了,这样横向纵向的逻辑便一致了,体验会好很多;
20230105补充:
发现按上面改算法后,还有几个地方要改,上面的改法layout高度还有微小的变化,所以layout的高度与x、y的值都需要重新计算,其实就是看哪里还有margin,关键词为:this.rowHeight + this.margin[1],对应修改;
第一处修改: GridItem.vue文件的calcXY方法:
calcXY(top, left) {
const colWidth = this.calcColWidth();
// left = colWidth * x + margin * (x + 1)
// l = cx + m(x+1)
// l = cx + mx + m
// l - m = cx + mx
// l - m = x(c + m)
// (l - m) / (c + m) = x
// x = (left - margin) / (coldWidth + margin)
let x = Math.round((left - this.margin[0]) / (colWidth + this.margin[0]));
let y = Math.round((top - this.margin[1]) / (this.rowHeight + this.margin[1]));
// Capping
x = Math.max(Math.min(x, this.cols - this.innerW), 0);
y = Math.max(Math.min(y, this.maxRows - this.innerH), 0);
return {x, y};
},
改为:
calcXY(top, left) {
const colWidth = this.calcColWidth();
// left = colWidth * x + margin * (x + 1)
// l = cx + m(x+1)
// l = cx + mx + m
// l - m = cx + mx
// l - m = x(c + m)
// (l - m) / (c + m) = x
// x = (left - margin) / (coldWidth + margin)
let x = Math.round((left - this.margin[0]) / (colWidth + this.margin[0]));
let y = Math.round((top - this.margin[1]) / this.rowHeight );
// Capping
x = Math.max(Math.min(x, this.cols - this.innerW), 0);
y = Math.max(Math.min(y, this.maxRows - this.innerH), 0);
return {x, y};
},
第二处修改: GridItem.vue文件的calcWH方法:
calcWH(height, width, autoSizeFlag = false) {
const colWidth = this.calcColWidth();
// width = colWidth * w - (margin * (w - 1))
// ...
// w = (width + margin) / (colWidth + margin)
let w = Math.round((width + this.margin[0]) / (colWidth + this.margin[0]));
let h = 0;
if (!autoSizeFlag) {
h = Math.round((height + this.margin[1]) / (this.rowHeight + this.margin[1]));
} else {
h = Math.ceil((height + this.margin[1]) / (this.rowHeight + this.margin[1]));
}
// Capping
w = Math.max(Math.min(w, this.cols - this.innerX), 0);
h = Math.max(Math.min(h, this.maxRows - this.innerY), 0);
return {w, h};
}
改成:
calcWH(height, width, autoSizeFlag = false) {
const colWidth = this.calcColWidth();
// width = colWidth * w - (margin * (w - 1))
// ...
// w = (width + margin) / (colWidth + margin)
let w = Math.round((width + this.margin[0]) / (colWidth + this.margin[0]));
let h = 0;
if (!autoSizeFlag) {
h = Math.round((height + this.margin[1]) / this.rowHeight);
} else {
h = Math.ceil((height + this.margin[1]) / this.rowHeight);
}
// Capping
w = Math.max(Math.min(w, this.cols - this.innerX), 0);
h = Math.max(Math.min(h, this.maxRows - this.innerY), 0);
return {w, h};
}
第三处修改: GridLayout.vue文件的containerHeight方法:
containerHeight: function () {
if (!this.autoSize) return;
// console.log("bottom: " + bottom(this.layout))
// console.log("rowHeight + margins: " + (this.rowHeight + this.margin[1]) + this.margin[1])
const containerHeight = bottom(this.layout) * (this.rowHeight + this.margin[1]) + this.margin[1] + 'px';
return containerHeight;
}
改成:
containerHeight: function () {
if (!this.autoSize) return;
// console.log("bottom: " + bottom(this.layout))
// console.log("rowHeight + margins: " + (this.rowHeight + this.margin[1]) + this.margin[1])
const containerHeight = bottom(this.layout) * this.rowHeight + this.margin[1] + 'px';
return containerHeight;
}
这样改了以后,x、y方向的算法才能一致;
提醒:
这里有个问题要说明一下,小编这里是因为使用的行高r很小,加了m以后,乘积变化大,因此高度突变得厉害,倘若r是默认的150,加10px,突变也不是很大,这样一来默认的方法用起来也没问题,大家根据自己的使用场景选择不同的计算公式即可;
小编测试过这样改之后也不会影响其他逻辑,大家可以多做尝试;
注:小编使用这个组件由于需求比较灵活,所以已经将vue-grid-layout的源码拷进来直接使用了,方便修改和扩展;
好了,今天就写到这,快去试试吧