flex 布局 自适应 靠左对齐

17,589 阅读3分钟

需求

假设我有5个300px*300px的卡片,需要根据屏幕宽度自适用平铺排列,并在换行的时候靠左对齐。

不能用js实现,用js就太简单了,而且代码会比较啰嗦和让人困扰。

需求

核心知识

  • 利用flex容器的justify-content: flex-startflex-wrap: wrap完成自动换行靠左需求
  • 利用calc和margin-left完成子项目平铺需求

单单利用justify-content: space-evenly完成不了需求

直觉上,利用这个属性和wrap就可以满足,但实际上不可以,因为会每行各自计算空隙,这样会导致最后一行对不齐,效果如下

space-evenly

利用flex-startmargin-left: calc实现

1. 数学公式

flex-start可以满足我们靠左对齐的需求。接下来,我们只要完成间距的需求就可以了。子项目宽度是固定的300px,那么根据容器的宽度,肯定就能计算出来间距了,例如宽1000px的容器,那肯定只能容纳3个子项目,剩余100px空间,一个四个空隙,那每个空隙就是25px。

  • conWidth 容器宽度
  • itemWidth 子项目宽度
  • 每行子项目个数 n = Math.floor(conWidth/itemWidth)
marginLeft
= \frac {conWidth - n \times itemWidth}{n + 1}
= \frac {conWidth - Math.floor(conWidth/itemWidth) \times itemWidth}{Math.floor(conWidth/itemWidth) + 1}

嗯,貌似是搞定了,我们转化成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项目使用。