基于 CSS flexbox 实现响应式图片列表

235 阅读13分钟

前言

在现代 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-sizingborder-box ,这样各个元素的内边距和边框不会影响元素的大小,还可以防止图片从父元素中内容溢出。在样式代码中,我们还设置了一些css变量,就是 :root 中的内容,这样可以方便我们进行样式定义和维护。

4. flex容器样式

至此,我们已经做好了应用flexbox 属性来创建我们的图片列表,接下来我们会一步一步来实现我们的列表展示功能。在上面定义的HTML结构中,.img-gallery 元素用来包裹所有图片项目,这是一个flex容器,所以我们会将它的 display 设置为 flex,同时给它添加更多的 flexbox 属性,如 flex-wrapgap

: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:2object-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-shrinkflex-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)
    );
}

计算好宽度之后,我们来看看 figurefigcaption 元素的样式,它们本例中用来包裹图片和图片标题,我们去除 figure 元素的外边距,同时给 figcaption 元素设置一些样式:

.img-gallery__item figure { 
  margin: 0 
}

.img-gallery__item figcaption {
  margin-top: .5rem;
  font-weight: bold;
}

7. 屏幕自适应样式

在CSS布局中,我们有多种方式来实现自适应的样式,如百分比、remvw 等,本文中,我们采用另一种比较常见的自适应样式——媒体查询属性。通过媒体查询,我们根据不同屏幕尺寸,通过改变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,这是把toprightbottomleft设置为0的快捷方式,而且会让它完全占据其容器的内容区域。然后,我们通过 flexbox 属性将图片标题的文字内容垂直水平居中显示,而且删除了上边距,也是为了让图片标题能够完全占据容器的内容区域。为了使悬停过渡的交互性稍强一些,我们使用了缩放变换属性和不透明度。

.img-gallery__item:hoverfigcaption { scale: 1; opacity: 1; }

最后,我们调整了悬停状态,将标题缩放到 100%。现在让我们来看看最终的显示效果:

9. 超进化——宽度不固定,高度固定的图片列表展示

在前面的示例中,我们使用 aspect-ratio 属性固定了每个图片的宽高比,但是在实际应用场景中,图片的宽高比不一定是固定的,这对于需要展示不同宽高比的场景来说,似乎不是那么的友好。接下来我们就改进一下写法,让不同宽度的项目也可以灵活在列表中展示。

首先,由于图片宽度不是固定的,我们去掉flex项目 .img-gallery__itemflex-basis 计算公式,让其能够灵活伸缩,为了保证布局不会乱掉,我们还要给它添加一个固定高度。现在每行的项目数会灵活变化,因此,我们也不需要媒体查询去调整项目数量。

:root {
  --gallery-item-height: 250px;
}

.img-gallery { ... }

.img-gallery__item {
  flex: auto;
  height: var(--gallery-item-height);
}

在这段CSS代码中,flexauto 值与等同于 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-basisflex-growflex-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布局或者专用的行业标准解决方案。

本文如有错误,欢迎各位指正!