1、传列数,根据列数计算元素容器宽度
好处是子元素可以写百分比宽度,不用固定某一种宽度,反正知道列数通过计算间距就能得到外层容器的宽度。
举个简单的例子:
(ps:以下用例皆在html中去模拟,就不另外起react或者vue应用了,主打一个轻便。)
在瀑布流下面的栅格布局
- 先尝试固定列数3,并且固定宽高100px
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
.item {
color: white;
background-color: violet;
width: 100px;
height: 100px;
margin: 2px;
text-align: center;
}
.waterfall {
display: flex;
flex-wrap: wrap;
}
</style>
</head>
<body>
<div class="grid waterfall"></div>
</body>
<script>
const column = 3;
const list = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
];
const UIList = list.map((item) => {
return `<div class="item">${item}</div>`;
});
const renderList = new Array(column);
const finalList = UIList.forEach((item, childIndex) => {
const realIndex = childIndex % column;
if (!renderList[realIndex]) {
renderList[realIndex] = [];
}
renderList[realIndex].push(item);
console.log("renderList", renderList);
});
const html = renderList.map((item) => {
return `<div class="waterfall-item">${item.join("")}</div>`;
});
document.querySelector(".grid").innerHTML = html.join("");
</script>
</html>
得到的结果:
好,写法ok,让我们试试列数固定(通过api传入),子元素宽度根据公式计算呢。
理论计算公式:元素宽度 = (屏幕宽度 - 左右间距宽度 - 列之间的间距)/ 列数
注意3列的话,计算列的宽度就是 2个列宽,自己体会。
好!理论成立,实践开始~!
我们修改下css,让item元素的宽高不在固定。
.item {
color: white;
background-color: violet;
height: auto;
margin: 2px;
text-align: center;
}
于是我们得到了上面一坨,因为宽度和高度根据内容自适应了,所以只包裹了填充的数字。
嗯,在意料之中。
那么关键的计算来了,且看下面的js:
const column = 3; // 列数
const screenWith = window.innerWidth; // 屏宽
const paddingLeft = 12; // 页面左间距
const paddingRight = 12; // 页面右间距
const columnWith = 8; // 列之间的间距
我们先设定页面左右间距为12(px),列间距为8(px).
然后我们就可以计算单个元素容器宽度了:
// 四舍五入并且精确到2位小数,防止列数过多而误差过大
const itemWidth =
Math.round(
((screenWith - paddingLeft - paddingRight - columnWith * (column - 1)) /
column) *
100
) / 100;
然后将元素的间距设置成和js里写的一致(当然你也可以把他们开放成动态的api):
完整demo:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
.item {
color: white;
background-color: violet;
height: 50px;
margin-bottom: 8px;
text-align: center;
}
.item:last-child {
margin-bottom: 0;
}
.waterfall {
display: flex;
flex-wrap: wrap;
padding: 0 12px;
}
.waterfall-item {
margin-left: 8px;
}
.waterfall-item:first-child {
margin-left: 0;
}
</style>
</head>
<body>
<div class="grid waterfall"></div>
</body>
<script>
const column = 3;
const list = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
];
const screenWidth = window.innerWidth;
const paddingLeft = 12;
const paddingRight = 12;
const columnWidth = 8;
const itemWidth =
Math.round(
((screenWidth - paddingLeft - paddingRight - columnWidth * (column - 1)) /
column) *
100
) / 100;
console.log("itemWidth", itemWidth);
const UIList = list.map((item) => {
return `<div class="item" style="width: ${itemWidth}px;">${item}</div>`;
});
const renderList = new Array(column);
const finalList = UIList.forEach((item, childIndex) => {
const realIndex = childIndex % column;
if (!renderList[realIndex]) {
renderList[realIndex] = [];
}
renderList[realIndex].push(item);
});
const html = renderList.map((item) => {
return `<div class="waterfall-item">${item.join("")}</div>`;
});
document.querySelector(".grid").innerHTML = html.join("");
</script>
</html>
效果:
测试高度不一样的栅格效果
增加修改以下代码,意思是给5的倍数的子元素高度设置到100px
.more-height {
height: 100px;
}
const UIList = list.map((item, index) => {
return `<div class="item ${
index % 5 === 0 ? "more-height" : ""
}" style="width: ${itemWidth}px;">${item}</div>`;
});
效果:
不错,符合预期~
如果我们连列数colum都不想传了咋办?能不能自适应屏幕尺寸设置列数
答案当然是肯定的,前提你要和设计师商量好什么尺寸下出多少列。
比如我们规定:
320px以下,展示2列
320~600px, 展示3列
600~920px,展示4列
920px以上,还是4列,但是容器宽度增加,这时候就需要子元素也做了响应式,否则会造成容器空旷很多。
使用以下方法,动态确定colum列数:
let column = 2;
function handleResize() {
const screenWidth = window.innerWidth;
if (screenWidth < 320) {
column = 2;
} else if (screenWidth >= 320 && screenWidth < 600) {
column = 3;
} else {
column = 4;
}
}
// 监听窗口大小变化
window.addEventListener("resize", handleResize);
// 页面加载时执行一次屏幕尺寸判断
handleResize();
看看效果:
320以下展示2行:
320~600 展示3行:
600~920 展示4行:
920往上还是4行,但是单元格宽度增加:
上面的唯一缺点大概是因为是js控制,页面只要不刷新,即使尺寸变了也不会去动态的计算列数,在移动端影响不大~
2、根据子元素宽度,自动适应列数
如果是采用这种方案,那么子元素必须显示的定义了宽度,即只有子元素存在定义的宽度时才能正确计算。
这种方法则对子元素要求很高,需要子元素定宽并且适配了各种各样屏幕宽度下的尺寸。
我们的计算规则则变成了:
列数 = (屏幕宽度 - 页面左右间距- (列数-1) *列间距) / 子元素宽度(固定)
该公式解出来变成:
列数 = (屏宽 - 左右间距 + 列间距) / (子元素宽度+列间距)
假定我们各项数据值是:
const screenWith = window.innerWidth;
const paddingLeft = 12; // 左间距
const paddingRight = 12; // 右间距
const columnWidth = 8; // 列宽
const itemWidth = 120; // 子元素宽度
计算规则如下:
// 列数必须向下取整,否则会放不下
column = Math.floor(
(screenWith - paddingLeft - paddingRight + columnWidth) /
(itemWidth + columnWidth)
);
像上面这种就是剩余宽度明显不够一列被向下取整给截断了。
那么有没有很好的解法呢,目前看来如果子元素定宽,不太好自适应的。
非要定宽可以考虑:
1、列间距自适应
2、整体布局居中
另外在设置子元素宽度时
计算失败的几种情况:
1、子元素定义了100%宽度,旨在根据容器宽度去定宽,但是容器也没有定宽。
2、子元素没有设置宽度导致计算失败,这时候会写一个兼容值,那么固定就会获取这个兼容值。
以上就是个人碎碎念~
希望有所收获!