需求
假设我有5个300px*300px的卡片,需要根据屏幕宽度自适用平铺排列,并在换行的时候靠左对齐。
不能用js实现,用js就太简单了,而且代码会比较啰嗦和让人困扰。

核心知识
- 利用flex容器的
justify-content: flex-start和flex-wrap: wrap完成自动换行靠左需求 - 利用calc和margin-left完成子项目平铺需求
单单利用justify-content: space-evenly完成不了需求
直觉上,利用这个属性和wrap就可以满足,但实际上不可以,因为会每行各自计算空隙,这样会导致最后一行对不齐,效果如下

利用flex-start和margin-left: calc实现
1. 数学公式
flex-start可以满足我们靠左对齐的需求。接下来,我们只要完成间距的需求就可以了。子项目宽度是固定的300px,那么根据容器的宽度,肯定就能计算出来间距了,例如宽1000px的容器,那肯定只能容纳3个子项目,剩余100px空间,一个四个空隙,那每个空隙就是25px。
- conWidth 容器宽度
- itemWidth 子项目宽度
- 每行子项目个数 n = Math.floor(conWidth/itemWidth)
嗯,貌似是搞定了,我们转化成css中calc就是:
margin-left: calc((100% - Math.floor(100% / 300px) * 300px) / Math.floor(100% / 300px))
但是,calc根本不支持这样:
- calc没有Math.floor方法
- calc中除法后面必须是数字
2. 利用@media 媒体查询解决每行个数问题
既然无法通过calc直接计算出来n,那就想办法跳过这一步。这样我们只需要完成左边的公式就行了。
嗯,因为我们子项目是定宽的300px,那么:容器少于900px,就只能放2个,少于1200px,就只能放3个。。。。 多写几个类似下面的媒体查询就可以了
@media screen and (max-width: 1200px) and (min-width: 900px) {
item { --n: 3; }
}
然后我们的margin-left就可以简化成如下calc支持的左边的公式了
margin-left: calc((100% - var(--itemWidth) * var(--n)) / (var(--n) + 1));
3. 最终代码
html
<div class="container">
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
/div>
css
.container {
background: gold;
display: flex;
justify-content: flex-start;
flex-wrap: wrap;
}
item {
outline: 1px solid black;
height: 300px;
background: red;
margin-top: 20px;
--itemWidth: 300px; /* 和项目宽度一致 */
width: calc(var(--itemWidth));
margin-left: calc((100% - var(--itemWidth) * var(--n)) / (var(--n) + 1));
}
@media screen and (max-width: 900px) {
item { --n: 2; }
}
@media screen and (max-width: 1200px) and (min-width: 900px) {
item { --n: 3; }
}
@media screen and (max-width: 1500px) and (min-width: 1200px) {
item { --n: 4; }
}
@media screen and (max-width: 8888px) and (min-width: 1500px) {
item { --n: 5; } /* 最多一行显示五个 */
}
通过缩放浏览器观察,已经实现自适用屏幕宽度了。

附
1. float布局也可以这样解决
思路一样,但是flex的其他特性就使用不了了
2. 如果无法使用css中的var,例如ie浏览器
那就只要把margin-left整体写到@media 媒体查询里即可
3. 使用grid布局更简单
如果使用grid布局,calc都不需要用到了,只要在@media中写好grid样式,在不同宽度下有不同列数即可 请看我的另外一篇文章
4. 可以简化公式
直接每个项目宽度 100% / n 即可,但是那样html代码就会很啰嗦,每个子项目都需要再套一层,作为真正的flex项目使用。