本文为原创, 遵循 CC 4.0 BY-SA 版权协议, 转载需注明出处: https://blog.csdn.net/big_cheng/article/details/130030120.
文中代码属于 public domain 无版权.
需求是要显示一组用户的头像+姓名+头衔, 显示成表格状, 要能自适应屏幕的宽度而调整列数, 最少2列最多5列.
直接的做法就是使用grid 布局 + media query, e.g.
<style>
......
@media screen and (max-width: 550px) {
.ct {grid-template-columns: repeat(4, 1fr);}
}
@media screen and (max-width: 450px) {
.ct {grid-template-columns: repeat(3, 1fr);}
}
@media screen and (max-width: 350px) {
.ct {grid-template-columns: repeat(2, 1fr);}
}
style>
因为嫌以上代码比较’rigid’, 尝试改用flex 布局来实现.
DOCTYPE html>
<html>
<body>
<style>
body {margin: 0;}
.ct {display: flex; flex-wrap: wrap;}
.ct>div {flex: 0 0 20%; border: 1px solid lightgray; text-align: center;}
.ct img {width: 100px;}
style>
<div class="ct">
<div>
<img src="test.png">
<BR>
Name
<BR>
Title
div>
<div>
<img src="test.png">
div>
<div>
<img src="test.png">
div>
<div>
<img src="test.png">
div>
<div>
<img src="test.png">
div>
<div>
<img src="test.png">
div>
div>
body>
html>
头像固定100px 宽(这里借用了作者以前一篇csdn 博文里的图片). 因为最多5列, 先暂定flex 的每项的flex-basis 为20%. 为了直观, 给每项加上浅灰边框. 布局自动折行.
为便于调试, 去掉body 的margin 以使屏宽等于容器宽.
Chrome 按F12, 屏宽调到600px, 效果图:
通过开发者工具 - Elements 查到每项宽122, 因为一行排不下5列只排4列, 剩下的宽度平分给每项 = 122 - 100 = 22(含边框线). 右侧剩余 = 600 - 122*4 = 600 - 488 = 112.
(在Elements 里) 修改每项的flex-basis 20% => 19%, 则可以变成5列.
回头看之所以20% 却不能排满5列, 原因是这个宽度默认是指div 内容区 的宽度 - 不包括padding 和border. 由于div 还有边框, 所以空间不够.
修改宽度为外框宽:
.ct>div {box-sizing: border-box; flex: 0 0 20%; ......}
则变成5列. 查到每列宽120, 正好占满600 的屏宽.
压缩屏宽至508 时变成4列. 查到每项宽102. 508 - 102*4 = 100剩余.
压缩到406 时变成3列, 剩余 = 406 - 102*3 = 100.
目前所有项都靠左排, 剩余空间在容器右侧. 修改让多余空间环绕每项(而不分配给每项):
.ct {......; justify-content: space-around;}
屏宽508 的效果图:
每项宽仍是102. 第一行排了4列. 列之间的间隙都等宽(但不等于行首/尾剩余的宽度).
第一行没有问题但第二行只有2项, 多余空间分配在项之间后使得这2项与第一行的各项不能(纵向)对齐. 我们希望折行后的项仍与上一行对应列的项对齐.
经过试验, 认为现有的flex 布局(2023年) 仅通过调整"justify-content" 做不出该效果.
经一段时间思索, 想到可在后面追加一些占位的项来’迫使’ 前面的项往左移.
需要使每一行的项数相等(除了完全是填塞项的最后一行 - 如果有的话). 因为每行最多5项, 所以需增加4项:
<style>
......
.ct>div {......}
.ct>div.fill {border: 0;}
......
style>
<div class="ct">
......
<div>
<img src="test.png">
div>
<div class="fill">div>
<div class="fill">div>
<div class="fill">div>
<div class="fill">div>
div>
508 的效果图:
各项宽102. 最后一行只余2个填塞项. 由于没有边框(也没有文字内容), 所有填塞项均看不到. 第二行后面的2个填塞项被布局拉到和前面的项等高.
406 的效果图:
这里出现了问题: 第二行仍和第一行不能纵向对齐.
经检查, 正常项宽度均是102, 填塞项宽度均是81.
406*20% = 81, 正常项均宽于这个20% - 这说明压缩不能’克服’ 项的最小宽. 所以我们也给填塞项设置一个最小宽:
.ct>div {......; flex: 0 0 20%; min-width: 102px; border: ......}
再试验各列均纵向对齐了.
目前可以压缩至只有1列. 为满足最少2列的需求, 给容器设置最小宽:
.ct {......; min-width: calc(102px*2);}
这样压缩至只有2列且没有任何剩余空间时, 如果再压缩则body 出现横向滚动条, 容器的宽度保持在204.
目前每次列数变化时(5列=>4列, 4列=>3列等) 各列均紧挨在一起显得拥挤, 可以添加列的间隔:
.ct {......; flex-wrap: wrap; column-gap: 4px; justify-content: ......;
min-width: calc(102px*2 + 4px);}
.ct>div {......; flex: 0 0 calc(20% - 4px); ......}
添加列间隔后, 容器的最小宽也需相应增加(2列 + 间隔). 注意每项的flex-basis 也要相应减去此间隔, 否则最多只能排4列达不到5列.
目前的效果应该已经达到需求了. 但是有必要再提一下图片丢失的情况.
屏宽406, 使某2张图找不到, 正常、禁用项的最小宽(.ct>div: min-width) 的对比效果图如下:
406*20% - 4 = 77, 禁用最小宽且图片丢失时项的宽度就是此值. 如果忘记考虑图片丢失的情况, 可能会造成布局错位. 我们前面为了让列纵向对齐设置了min-width, 正好也防止了这个问题.
当能容纳5列时, 随着屏宽增加增加的空间都分给了各项, 各项的宽度大于102.
一旦开始折行(4、3、2列), 各列的宽度始终是102, 如果有剩余空间都用于"space-around".
就是列宽不能保证固定.
DOCTYPE html>
<html>
<body>
<style>
body {margin: 0;}
.ct {display: flex; flex-wrap: wrap; column-gap: 4px; justify-content: space-around;
min-width: calc(102px*2 + 4px);}
.ct>div {box-sizing: border-box; flex: 0 0 calc(20% - 4px); min-width: 102px;
border: 1px solid lightgray; text-align: center;}
.ct>div.fill {border: 0;}
.ct img {width: 100px;}
style>
<div class="ct">
<div>
<img src="test.png">
<BR>
Name
<BR>
Title
div>
<div>
<img src="test.png2">
div>
<div>
<img src="test.png">
div>
<div>
<img src="test.png">
div>
<div>
<img src="test.png">
div>
<div>
<img src="test.png2">
div>
<div class="fill">div>
<div class="fill">div>
<div class="fill">div>
<div class="fill">div>
div>
body>
html>