需求描述
在业务当中经常会遇到下面的布局,例如商品的展示页:
其中有两个特点:
- 项目的数量是接口动态返回的
- 项目和项目容器都是定宽的
实现过程
以五个项目为例:
<div class="container">
<div class="item">One</div>
<div class="item">Two</div>
<div class="item">Three</div>
<div class="item">Four</div>
<div class="item">Five</div>
</div>
如果采用 flex 布局,CSS 代码会这样写:
.container {
width: 450px;
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.item {
width: 100px; /* 项目定宽 */
height: 100px;
border: solid;
}
最终的效果如下:
视觉优化
可以看到,项目整体在容器内并未居中,导致整个视觉向容器左侧倾斜,因此更好的方式是不指定 gap,而是让项目每一行占满容器,多出来的空白作为间隙即可,即使用 space-between 的方式:
.container {
width: 450px;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
然而,当最后一行项目的数量不足的时候,布局又跟预期不一致了:
因为产品希望的效果是:
那怎么办呢?网上搜了一下,stackoverflow 上面有个高分回答,给出的代码如下:
.container::after {
content: "";
flex: auto;
}
经过实际测试,发现上面的代码仅在没有空隙的时候是可以的,而在我们的 case 中效果如下:
显然不符合需求,如果坚持采用 flex 布局的话,确实是无解的了。但如果换一个角度的话,还可以有两种解决方案:
- 用 JavaScript 补齐最后一行缺少的数量,然后设置其为不可见
- 采用 grid 布局
JS 补齐
假设每一行放置的项目数量为 column,后端返回的项目数组为 arr,补齐函数如下:
function paddingArray(arr, column = 3) {
const lack = arr.length % column
for (let i = 0; i < lack; i++) {
arr.push({ type: 'ghost' })
}
}
在渲染的时候,当遇到 type 为 ghost 的时候,表示幽灵节点,可以用 visibility: hidden 或者 opacity: 0 来实现不可见的效果:
function BtnCell({ type, text }) {
if (type === 'ghost') return <div className="btn-cell ghost-cell"></div>
return (
<div className="btn-cell" onClick={handleCellClick}>
<div>{text}</div>
</div>
)
}
示例 CSS 如下:
.btn-cell {
box-sizing: border-box;
width: @cell-width;
height: @cell-height;
border-radius: 20px;
&.ghost-cell {
visibility: hidden;
}
}
grid 布局
从 caniuse 上看,栅格布局的浏览器兼容性已经很高了:
如果不考虑低版本浏览器的话,可以直接上生产环境,因为使用栅格布局实现上述效果非常简单:
.container {
width: 450px;
display: grid;
grid-template-columns: repeat(auto-fill, 100px);
justify-content: space-between;
grid-gap: 20px;
}
这里面的关键在于 grid-template-columns 属性的设置,例如:
/* 自适应布局,平均分 12 列 */
grid-template-columns: repeat(12, 1fr);
/* 元素宽度 100px,如果一行放不下就换行 */
grid-template-columns: repeat(auto-fill, 100px);
最终的效果: