前言
在现代 Web 项目中,我们会经常遇到大量图片列表展示的场景,而开发者们一般会网格类型的布局来显示一系列的图片,以便用户可以轻松浏览它们。
在HTML5中有很多方法可以单独使用CSS创建这样的网格布局,本文将介绍如何使用 CSS flexbox 创建在所有设备上看起来适配都不错的响应式布局。在本文中,我们将通过实际示例来展示不同 flexbox 属性的效果,每个示例都使用 CSS 自定义属性来管理可重用的值,并且通过合并媒体查询来添加特定于屏幕的 CSS,并利用 CSS flexbox 属性来布局图片列表。
1. CSS flexbox 简介
通常来说,CSS Flexbox 经常应用于一行或一列的布局,它提供了一系列的 CSS 属性,能够让我们自由的伸缩、对齐flex容器内部的flex项目,它会根据flex容器的空间来响应式动态布局。但是,Flexbox 也可以将flex项目排列在多行上,实现类似网格的布局,这也是本文的重要内容之一。
接下来,首先让我们了解一些关于 CSS Flexbox 的重要概念:
- Flex容器:当一个HTML元素被设置为display: flex;或display: inline-flex;时,这个元素就变成了一个Flex容器。Flex容器具有一些关键属性,如flex-direction、flex-wrap、justify-content和align-items等,这些属性共同决定了Flex项目在容器中的排列方式和对齐方式。
- flex-direction属性定义了Flex项目在主轴上的方向。
- flex-wrap属性定义了Flex项目是否换行以及换行的方向。
- justify-content属性定义了Flex项目在主轴方向上的对齐方式。
- align-items属性定义了Flex项目在交叉轴方向上的对齐方式。
- Flex项目:Flex容器内的直接子元素称为Flex项目。Flex项目可以根据容器的空间自动调整其大小和对齐方式,以适应不同的屏幕尺寸和设备。
2. 具有统一图片尺寸的响应式图片列表
现在让我们先来确定图片列表的HTML结构,它是一个Flex容器,包裹了多个Flex项目,每个项目包含了许多图片,每张图片都会包裹在一个figure元素中,该元素带有一个可选的figcaption元素,用来关联图片的标题。
<div class="img-gallery">
<div class="img-gallery__item">
<figure>
<img src="..." width="..." height="..." alt="...">
<figcaption>...</figcaption>
</figure>
</div><!-- .img-gallery__item -->
<div class="img-gallery__item">
<!-- ... -->
</div><!-- .img-gallery__item -->
</div><!-- .img-gallery -->
注意,指定图片的宽度和高度属性是一种很好的做法,可以防止累积布局偏移 (CLS) 问题,从而提高网页的核心 Web 指标 (CWV) 分数。如果您不确定要为这些属性提供哪些值,只需添加原始图片尺寸,浏览器引擎将处理每个图片的间距和纵横比。
现在,如果我们用图片完善结构,由于还没有使用CSS样式,所有图片将会堆叠挤在一起。
3. 添加CSS Resst(样式重置)
为了处理不同浏览器上一些元素默认样式不一致的问题,我们首先重置部分CSS样式:
# 全局CSS变量
:root {
--body-leading: 1.6;
--container-padding: 1.5em;
--container-width: 1260px;
}
* {
&,
&::before,
&::after {
box-sizing: border-box;
}
}
body {
margin: 0;
font: 1em/var(--body-leading) sans-serif;
}
img {
max-width: 100%;
vertical-align: middle;
height: auto;
}
.page-container {
padding: var(--container-padding);
max-width: var(--container-width);
margin-right: auto;
margin-left: auto;
}
这部分样式设置了所有元素的 box-sizing
为 border-box
,这样各个元素的内边距和边框不会影响元素的大小,还可以防止图片从父元素中内容溢出。在样式代码中,我们还设置了一些css变量,就是 :root
中的内容,这样可以方便我们进行样式定义和维护。
4. flex容器样式
至此,我们已经做好了应用flexbox 属性来创建我们的图片列表,接下来我们会一步一步来实现我们的列表展示功能。在上面定义的HTML结构中,.img-gallery
元素用来包裹所有图片项目,这是一个flex容器,所以我们会将它的 display
设置为 flex
,同时给它添加更多的 flexbox 属性,如 flex-wrap
和 gap
:
:root {
--gallery-gap: 1.5em;
}
.img-gallery {
display: flex;
flex-wrap: wrap;
gap: var(--gallery-gap);
}
这段CSS中,我们给 .img-gallery
的元素设置了一些 flex 属性,flex-wrap
属性能够让flex项目在flex容器空间不够时进行换行排列,gap
属性设置行与列之间的间隔为 1.5em。
默认情况下,flex容器的方向或主轴是“row”,因此所有flex项目会立即水平对齐,我们也可以通过 justify-content
属性来设置flex项目在主轴上是中心对齐还是末端对齐。
5. 图片样式
我们使用 aspect-ratio
属性来保证每个图片的宽高比为 3:2
,object-fit
属性来保证图片能够完全覆盖图片容器区域,也就是 .img-gallery__item
元素,并且避免图片出现拉伸变现,溢出容器部分的图片将会隐藏:
:root {
--gallery-item-border-radius: .4em;
}
.img-gallery__item {
img {
aspect-ratio: 3 / 2;
object-fit: cover;
border-radius: var(--gallery-item-border-radius);
}
}
6. flex项目样式
我们定义一个CSS变量,用来控制每行的项目数,当屏幕尺寸由小变大时,我们可以去改变每行项目的数量:
:root {
--gallery-items-per-row: 1;
}
在此基础上,我们还需要考虑每行的项目数量以及项目之间的间隔,我们需要计算每个项目的完美宽度(或弹性基础)来适应屏幕宽度:
S = 100% - g * (n - 1)
S 是内容的总宽度,g 是间隔宽度,n 是每行的项目数,我们将此值除以项目数量就可以计算出每个项目 (Si) 所占的宽度:
Si = S / n
计算出宽度后,我们为每个flex项目提供一个 flex-basis
,该属性设置flex项目的初始大小。为了保证flex项目的宽度是固定的,我们还需要设置flex-shrink
和 flex-grow
来防止每个项目自动伸缩:
// 根据CSS变量计算每一项的宽度,而且防止拉伸变形
.img-gallery__item {
flex: 0 0
calc(
100% / var(--gallery-items-per-row)
- var(--gallery-gap) *(var(--gallery-items-per-row) - 1) / var(--gallery-items-per-row)
);
}
计算好宽度之后,我们来看看 figure
和 figcaption
元素的样式,它们本例中用来包裹图片和图片标题,我们去除 figure
元素的外边距,同时给 figcaption
元素设置一些样式:
.img-gallery__item figure {
margin: 0
}
.img-gallery__item figcaption {
margin-top: .5rem;
font-weight: bold;
}
7. 屏幕自适应样式
在CSS布局中,我们有多种方式来实现自适应的样式,如百分比、rem
、vw
等,本文中,我们采用另一种比较常见的自适应样式——媒体查询属性。通过媒体查询,我们根据不同屏幕尺寸,通过改变CSS变量--gallery-items-per-row
,来指定当前屏幕尺寸下,每行合适的flex项目数量,然后自动计算每一项的flex-basis
宽度是多少:
@media only screen and (width >= 1024px) {
.img-gallery {
--gallery-items-per-row: 4;
}
}
@media only screen and (768px < width < 1024px) {
.img-gallery {
--gallery-items-per-row: 3;
}
}
@media only screen and (540px < width < 768px) {
.img-gallery {
--gallery-items-per-row: 3;
}
}
到此为止,我们已经完成了大部分的样式和布局,得到了一个能够根据屏幕尺寸自适应的图片列表展示效果:
接下来我们可以进一步完善示例的样式。
8. 进化——添加图片标题样式
现在图片的标题是紧挨着图片下方的,感觉不是很美观,现在我们要让图片标题隐藏起来,只有当鼠标移动到图片上的时候才会显示对应的标题。接下来我们会来实现好几种图片标题的样式。
8.1 经典样式
为了让图片标题显示的时候不影响图片列表的布局,我们需要让图片标题脱离文档流,自然而然我们想到用绝对定位的方式来实现,这样图片标题就不会显示在图片外面,而是显示在图片上面,同时,为了防止内容溢出,我们还给图片容器设置了 overflow: hidden
。
.img-gallery__item {
position: relative;
overflow: hidden;
border-radius: var(--gallery-item-border-radius);
}
.img-gallery__item figcaption {
position: absolute;
inset: auto auto 0 0;
width: 100%;
padding: 1rem;
}
为了让图片上面显示的标题清晰易读,我们可以为它们设置一个深色背景,并切带有一点不透明度和更亮的文本颜色:
:root {
--gallery-caption-bg-color: hsl(0 0% 0% / 90%);
--gallery-caption-text-color: white;
}
.img-gallery__item figcaption {
background-color: var(--gallery-caption-bg-color);
color: var(--gallery-caption-text-color);
}
当鼠标移动到图片上时,图片标题应该显示。为了让显示的效果更加的柔和不显突兀,我们可以让图片标题不透明度从0开始,过渡到 100%:
.img-gallery__item figcaption {
opacity: 0;
transition: opacity .25s ease-in-out;
}
.img-gallery__item:hover figcaption {
opacity: 1;
}
ok,我们可以来看看最终的效果了:
8.2 蒙版样式
和经典样式想比,蒙版样式需要完全覆盖在图片上面,同时蒙版的中心和图片的中心需要对齐,让我们对上面的样式做一些小的调整:
:root {
--gallery-caption-font-size: 1.5em;
}
.img-gallery__item figcaption {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
margin-top: 0;
font-size: var(--gallery-caption-font-size);
color: var(--gallery-caption-text-color);
background-color: var(--gallery-caption-bg-color);
transition: opacity 0.25s ease-in-out, scale 0.15s ease-in-out;
scale: 0;
opacity: 0;
}
在这段样式中,我们将图片标题的 inset
属性设置为0,这是把top
、right
、bottom
、left
设置为0的快捷方式,而且会让它完全占据其容器的内容区域。然后,我们通过 flexbox 属性将图片标题的文字内容垂直水平居中显示,而且删除了上边距,也是为了让图片标题能够完全占据容器的内容区域。为了使悬停过渡的交互性稍强一些,我们使用了缩放变换属性和不透明度。
.img-gallery__item:hoverfigcaption { scale: 1; opacity: 1; }
最后,我们调整了悬停状态,将标题缩放到 100%。现在让我们来看看最终的显示效果:
9. 超进化——宽度不固定,高度固定的图片列表展示
在前面的示例中,我们使用 aspect-ratio 属性固定了每个图片的宽高比,但是在实际应用场景中,图片的宽高比不一定是固定的,这对于需要展示不同宽高比的场景来说,似乎不是那么的友好。接下来我们就改进一下写法,让不同宽度的项目也可以灵活在列表中展示。
首先,由于图片宽度不是固定的,我们去掉flex项目 .img-gallery__item
的 flex-basis
计算公式,让其能够灵活伸缩,为了保证布局不会乱掉,我们还要给它添加一个固定高度。现在每行的项目数会灵活变化,因此,我们也不需要媒体查询去调整项目数量。
:root {
--gallery-item-height: 250px;
}
.img-gallery { ... }
.img-gallery__item {
flex: auto;
height: var(--gallery-item-height);
}
在这段CSS代码中,flex
的 auto
值与等同于 flex-basis: auto
,flex项目会根据空间大小灵活伸缩。现在,flex项目会填满flex容器的空间,但是我们会发现一个问题,最后一行的几个flex项目会伸长直到填充满最后一行的空间。为了解决这个问题,我们可以在flex容器上使用::after
伪元素,让它拥有更大的flex-grow
值,这会让伪元素占据额外的空间,并确保最后一行的最后几项没有伸长的空间。
.img-gallery::after {
content: "";
flex-grow: 999;
}
现在我们可以看到最终的结果:
10. 究极进化——瀑布流布局
在上一个例子中,我们不使用媒体查询也实现了图片在保持原始宽高比的情况下,根据屏幕尺寸自适应的布局,而且每行的图片数量不固定,本例中,我们将尝试将 flexbox 和媒体查询结合起来使用,保持图片宽高比不变,最终实现瀑布流布局。
在这种情况下,我们需要将所有图片的高度累加,再除以布局中的列数,就可以获得了整个布局的高度,这个计算用CSS实现不太可行,需要大量的尝试,不过幸运的是,我们可以借助于JS来弥补CSS不足的能力。
首先我们来调整一下图片容器的样式:
.img-gallery {
display: flex;
flex-flow: column wrap;
}
.img-gallery__item img {
border-radius: var(--gallery-item-border-radius);
object-fit: cover;
}
在上面的代码中,我们没有设置 flex-basis
、flex-grow
或 flex-shrink
属性,而是使用了 width 属性来设置画廊项的宽度。同样的,我们也没有设置 aspect-ratio
属性,以便项目可以使用原始图片大小来缩放。
接下来我们需要根据屏幕尺寸去计算每行的项目个数,这边和前面例子不一样的是,我们不再使用媒体查询来去设置每行的项目个数,因为通过纯CSS是无法去计算图片列表布局容器的高度的,而是通过一个开源JS插件——FlexMasonry来实现。FlexMasonry的核心是CSS Flexbox,这是一种用于定义容器内元素的布局方式,特别适合处理复杂的流式布局。JavaScript部分主要负责动态计算布局的高度,以及在不同屏幕尺寸下切换列数,使得布局能自适应适配各种屏幕分辨率设备。
// 引入库样式
<link rel="stylesheet" href="https://unpkg.com/flexmasonry/dist/flexmasonry.css">
// 引入库JS
<script src="https://unpkg.com/flexmasonry/dist/flexmasonry.js"></script>
<script>
FlexMasonry.init(".img-gallery", {
breakpointCols: {
"min-width: 1024px": 3,
"min-width: 768px": 2,
},
});
</script>
通过这JS插件,我们可以计算出图片列表布局中列数以及每列的高度,而且通过order flexbox属性为每一项都指定了正确的顺序。最后,让我们看看最终完成的瀑布流效果:
11. 总结
本文中,我们使用CSS flexbox实现了三种响应式图片列表布局,能够在不同屏幕尺寸的设备上比较完美的展示,最后一种布局也是现实场景中使用非常多的瀑布流布局。
CSS flexbox 是一种现代化的布局模式,常用于创建响应式的一维布局(无论是水平还是垂直)。它具有简单的功能,提供了对项目的对齐、方向、排序、尺寸调整等方面的强大控制。但是,它并不适合需要对项目流、对齐方式和其他自定义方面进行更精细控制的复杂网格布局,比如瀑布流布局。对于此类情况,建议使用 CSS grid布局或者专用的行业标准解决方案。
本文如有错误,欢迎各位指正!