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、子元素没有设置宽度导致计算失败,这时候会写一个兼容值,那么固定就会获取这个兼容值。
以上就是个人碎碎念~
希望有所收获!