自定义响应式布局逻辑
欢迎各位交流分享
1 目标
单行时,框体保持一定间距:
双行或者多行时,每列的宽度以最大宽的元素的宽度为准 :
2 方案
2.1 方案一:Js + Css + Flex布局封装组件完成
经过可行性分析后,发现控制起来不是特别容易,需要每行的每个元素设定宽度,成本有点高。
2.2 方案二:Css + Grid布局封装组件完成
并没有找到,列数自适应的方案,不过倒是了解到有栅格化布局,以及UX设计师的职业,很感兴趣。
猜想大概率是浏览器没想要支持这类操作,因为性能方面不是很优异。需要涉及不确定性的动态分配,
2.3 方案三:Js + Css + Grid布局封装组件完成
在方案一的基础上,减少实施难度得到的方案,如果替换成方案一的实施方案,也可以,思路是一样的,只不过grid更方便。
未进行组件封装,如果组件化,可能需要限制onresize的绑定方式配合,以下代码实现
<!DOCTYPE html>
<html>
<head>
<style>
.box {
display: flex;
flex-wrap: wrap;
}
.box>.item {
width: 150px;
height: 50px;
margin-top: 8px;
margin-right: 16px;
border: 1px solid red;
display: flex;
justify-content: center;
align-items: center;
}
.box>.item:nth-child(2n+1) {
width: 100px;
}
.box>.item:nth-child(3n+1) {
width: 80px;
}
</style>
</head>
<body>
<div id="app">
<div ref="box" class="box" :style="styleOfBox">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
<div class="item" style="width: 550px">6</div>
<div class="item">7</div>
</div>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data() {
return {
isReized: true
};
},
mounted() {
console.log("init");
// 监听窗口变化,当窗口变化时,通过计算属性清空grid式样
window.onresize = () => {
console.log("resize");
this.isReized = true;
}
// 页面初次触发根据flex布局的计算,生成grid式样
this.isReized = false;
},
computed: {
// isReized, true时清空grid布局,false时根据flex布局计算并生成grid式样
styleOfBox() {
console.log("computed");
// 重新加载时清空grid布局
if (this.isReized) {
return {};
}
let box = this.$refs.box;
let children = [...box.children];
if (children.length > 0) {
// 计算‘最大列数’,因为之前写好了,所以暂时继续使用flex布局下对象的坐标检测行边缘,得到最大列数
// 可以考虑使用‘元素宽度’与‘外框宽度’比较,得到最大列数
let topOfFirst = box.firstChild.offsetTop;
let maxColumnCount = children.findIndex(x => topOfFirst != x.offsetTop);
// 单行时,不进行grid布局切换
if (maxColumnCount == -1) {
return {};
}
let columnsWidth = new Array();
let boxWidth = this.getInnerWidth(box); // 获取边框内部可用宽度
let self = this;
// 涉及动态分配算法,不如提前进行设计上的合理规避,最终结果还不一定好看
// 对算法精通的大神,如果有兴趣,给小弟分享一下其他计算方案
// 计算合理可用的最大列数,以及对应的列宽,如果仅剩余一列,使用那一列的最大值作列宽
(function calculate(columnCount) {
// 暂时获取首行做列宽
columnsWidth = children.slice(0, columnCount).map(x=>self.getOffsetWidth(x));
// 计算最大满行数
let baseCount = parseInt(children.length / columnCount);
// 计算目前总行宽
let widthTotal = columnsWidth.reduce((total, x) => x + total, 0);
// 计算当前列数是否能满足要求
let isFail = columnsWidth.some((x, i) => {
// 遍历对应列的单元格,从第二行开始
for (let j = 1; j <= baseCount; j++) {
let currI = j * columnCount + i;
// 排除最后一行越界的可能性
if (currI < children.length) {
// 获取当前元素宽度
let currW = self.getOffsetWidth(children[currI]);
// 当前元素宽度大于当前列宽,则进行列宽修改,并计算合理性
if (currW > x) {
let updatedTotal = widthTotal - x + currW;
// 如果修改后总行宽不大于元素宽度则正式替换列宽
// 如果仅剩余一列,使用那一列的最大值作列宽
if (updatedTotal <= boxWidth || columnCount == 1) {
widthTotal = updatedTotal;
columnsWidth[i] = currW;
} else {
// 如果修改后总行宽大于元素宽度则此方案不可行
return true;
}
}
}
}
// 能走到这里,说明此方案可用
return false;
});
// 不满足要求时,重新减少一列进行重新计算,直到满足为止
isFail && calculate(columnCount - 1);
})(maxColumnCount);
return {
display: "grid",
gridTemplateColumns: columnsWidth.map(x => x + "px").join(" ")
};
}
return {};
}
},
updated() {
console.log("updated");
// 清空grid式样成功后,触发根据flex布局的计算,并生成新的grid式样
if (this.isReized) {
this.isReized = false;
}
},
methods: {
// 获取元素实际高度,包括margin
getOffsetWidth(el) {
let computedStyles = window.getComputedStyle(el);
let left = parseInt(computedStyles["margin-left"].slice(0, -2));
let right = parseInt(computedStyles["margin-right"].slice(0, -2));
return left + el.offsetWidth + right;
},
// 获取元素内部可用宽度,抛去边框和内边距之后
getInnerWidth(el) {
let computedStyles = window.getComputedStyle(el);
let leftP = parseInt(computedStyles["padding-left"].slice(0, -2));
let rightP = parseInt(computedStyles["padding-right"].slice(0, -2));
let leftB = parseInt(computedStyles["border-left"].slice(0, -2));
let rightB = parseInt(computedStyles["border-right"].slice(0, -2));
return leftP + leftB + el.offsetWidth + rightB + rightP;
}
},
});
</script>
</html>