你会经常用 marginpadding 来处理一组 HTML 元素的布局吗?如果是的话,这篇文章可能会为你打开另一个思路。

我们经常遇到的一个开发场景是,一堆标签等间距排列。

Tag List margin-left

如何控制它们之间的间距呢?可能会想到的一个方案是:

.tag {
  margin-left: 8px;
}

.tag:first-of-type {
  margin-left: 0;
}

看上去不错,并且处理了第一个标签左边距的影响。但是等等,如果标签发生换行了呢?我们需要处理两个新发生的异常状况:

Tag List wrap

上下间距除第一行之外的其它行第一个标签的左间距。让我们换一种处理方式:

.tag {
  margin-right: 8px;
  margin-bottom: 8px;
}

可见,我们从 margin-left 换成了 marign-right 以此来消除换行时首个标签的左对齐问题。增加了margin-bottom 来处理换行时标签上下间距问题。

但由此,我们出现了两个新的问题:

  1. 每行标签的最右侧必定会存在一个 8px 的间距,我们无法通过 :last-of-type 来控制。

  2. 最后一行标签存在无用的底部间距,无法通过标签自身来消除。

Tag List margin

让我们连带标签容器一同考虑调整一下,为容器设置一个负值的右间距和下间距,以此来消除其对外部布局产生的影响。

.tag-list {
  margin-bottom: -8px;
  margin-right: -8px;
}

Tag List negtive margin

虽然解决了问题,但是我们发现除了额外增加了一些 CSS 之外,还额外增加了属性值的维护成本。即使我们可以使用 CSS 自定义变量 来提取变量,降低一定的变更成本,但其可能隔段时间理解起来还需要思考一会儿。

有没有更简单,更灵活的方式呢?有。答案就是 flex(flexbox) + gap(grid-gap)

.tag-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

其不会产生额外的外间距影响。只对其内部元素之间的间距进行生效。

Tag List css flex gap

与通常所说的 Gird 布局不同,Grid 强制二维对齐,Flexbox 专注于轴内的空间分布,使用更简单的布局方式,可以使用基于内容大小的换行系统来控制其次轴,并依赖底层标记层次结构来构建更复杂的布局。

gap 限定了行和列之间的间隙。是 row-gapcolum-gap 的简写。与 margin 不同,即使在强制中断后,gap 也会被抑制,即如果是 LTR(从左至右)布局的话,最右侧不会存在间隙。

也正是其在第一个之前和最后一个之后没有间隙的特点(指的是隐式网格,即 Flexbox 的每行),也使得其可以更好的处理布局,而不用额外处理边界间隙溢出的情况。

让我们来看一下另外一个场景来感受一下 gap 的魅力。

当我们在做一个 IM 布局时,单个消息区域的组成包含以下几个部分,他们之间都是相同的间距。同时,其有左右布局两种显示方式,我们很容易想到对于 .message-layout--right 的子元素进行 margin-right的设置。

.message-layout {
  .message-bubble,
  .message-actions {
    margin-left: 8px;
  }
}

.message-layout--right {
  .message-bubble,
  .message-actions {
    margin-right: 8px;
  }
}

如果换成 gap 就不需要当布局方向发生变化时,而额外调整了。

.message-layout {
  .message-bubble,
  .message-actions {
    gap: 8px;
  }
}

最后

  • 设计组件时,不要做假设其出现场景的设计,例如 Tag 组件设置一个默认的右(或左)间距,以便当多个标签出现时有一个间距。但是当其单独使用,或有边缘对齐使用时,就需要覆盖掉,甚至还需要单独处理换行的场景。我在使用真实的组件库时就见到过这样的情况。

  • gap 也不是万能的,当面对同一方向上间距不同的设计时就无能为力了,虽然这种情况在一个“组”中不太常见。

  • 延伸阅读 CSS Grid Layout Module Level 1

最后更新于:
2021.11.10