健壮的 CSS

791 阅读10分钟

前言

Ahmad Shadeed 的原创文章 Defensive CSS 非常的干货和实用,在你每次动键盘写 CSS 之前,都应该把它拿出来温习一遍,作为你写 CSS 的指导。

如果你没有把这些作为写 CSS 的指导,虽然完成业务需求,但你就像造人的上帝,只造了骨架没有填充血液,人面对庞杂的世间,难免跑蹦跳,摔了是需要肉来减震的,只有一副骨架岂不分分钟散架了。

业务往往是最理想的 CSS 运行环境,部署到服务器面对复杂的网络环境,我们对 CSS 的要求,就像生活对人的要求,一个健壮完整的人才能承载生活的苦难,而只有健壮 CSS 才能面对复杂的网络环境。

间距(Spacing)

我们开发人员需要考虑不同的内容长度。这意味着,即使看起来不需要,也应该向组件添加间距。

在上图示中,左侧有一个标题右侧一个操作按钮。目前,看起来还可以。但是让我们看看当标题更长时会发生什么。

注意到文本离操作按钮太近了吗?您可能正在考虑换行展示,这个我们先不讨论。现在,让我们关注间距。

如果你让标题有间距同时文本截断,我们不会看到这样的问题。

.section__title {
    margin-right: 1rem;
}

对于这个组件,标题可以是一个词或多个词,甚至是一个长句子。为避免标题卡在右侧的图标上,最好添加margin-right: 1rem以防标题变长。

说到文本溢出截断,还有一个最常见的就是——长内容。

长内容(Long Content)

在构建布局时考虑长内容很重要。正如您在前面看到的,部分标题过长时会被截断,当然你也可以换行。

这是一个人名列表,现在看起来很完美。

但是,由于这是用户生成的内容,因此我们需要注意如何防御布局,以防万一太长。

见下图:

在这样的布局中,一致性很重要。为了实现这一点,我们可以简单地通过 usingtext-overflow和它的朋友来截断名称。

.username {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

提示,当文本截断的时候千万不要忘了处理提示,不然用户怎么看到全部内容来。

如果您有兴趣提高处理 CSS 中长内容的技能,我写了一篇关于该主题的详细文章

卡片上的文字(category tag)

还有一个长文本的例子:

对于卡片组件,您可能需要一个类别标签。您将如何处理内容的不同变化?考虑下图。

右边的卡片有一个很长的标题,这看起来不太好,因为它覆盖了缩略图的很大一部分。对于食品网站,图像非常重要。

这里有一些可能的解决方案,以防万一标签变得更长。

第一个解决方案是使用文本截断技术,max-width在 CSS 中使用 a 。第二个只使用max-width,但如果标签有很长的文本,它可能会失败。

.tag {
  max-width: 6.25rem;
  text-overflow: ellipsis;
  overflow: hidden;
  white-space: nowrap;
}

按钮(Buttons)

在某些情况下,您需要并排放置两个按钮。我们如何解释这种情况,而不margin默认添加按钮?

多亏了相邻兄弟组合器,让这很容易做到。它是说:“如果一个按钮与另一个按钮相邻,则为第二个按钮添加一个左边距,以防万一”。

.button + .button {
  margin-left: 1rem;
}

可滚动元素(Scrollable Element)

overflow 可拆分为 overflow-x 和 overflow-y 两者默认都是 visible,也就是说当内容溢出盒子不会产品滚动条。

当您需要使元素可滚动时,可能很想执行以下操作:

.element {
  height: 20rem;
  overflow-y: scroll;
}

这种方法的问题在于它会始终显示 scrollbar,即使内容不够长无法滚动。最好是使用auto关键字。这样,它可以通过以防万一的概念。

.element {
  height: 20rem;
  overflow-y: auto;
}

图片上的文字(HTML Image Fallback)

如果您用 <img> 来展示图片,而图片上有文字内容,但图片可能由于某种原因无法加载。在这种情况下,如果有后备怎么办?

img {
  background-color: #525252;
}

以防万一图像无法加载,background-color它将作为后备。

头像图片(Avatar Image)

这是一个非常常见的用例,其中头像可能看起来被拉伸压缩。 年度人气创作者榜单:

just-in-case-6.png

可以通过使用来避免object-fit: cover。它将充当以防万一的事情。没有它,图像会看起来很糟糕。

掘金的 2021 最佳创作者活动就是一个例子:

掘金 2021 创作者投票

图片最大宽度(Image maximum width)

防止图片被拉伸,作为一般规则,不要忘记设置max-width: 100%所有图像。这可以添加到您使用的 CSS 重置中。

img {
    max-width: 100%;
    object-fit: cover;
}

flex 布局换行(Flexbox Wrapping)

CSS flexbox 是当今最有用的 CSS 布局功能之一。

添加display: flex 布局在而是以世界已经是常见操作,但问题是当空间不足时,默认情况下这些子项不会换行。我们需要使用 flex-wrap: wrap

这是一个典型的例子。我们有一组应该并排显示的选项。

.options-list {
    display: flex;
}

当空间较少时,将发生水平滚动。这应该是意料之中的,实际上并不是一个“问题”。

请注意这些项目如何仍然彼此相邻。为了解决这个问题,我们需要允许 flex wrapping。

.options-list {
    display: flex;
    flex-wrap: wrap;
}

使用 flexbox 时的一般经验法则是允许换行,除非您想要滚动,但那是另一回事,所以尽量使用flex-wrap以避免意外的布局行为(在我们的例子中是水平滚动)。

锁定滚动链接(Lock scroll chaining)

您是否曾经打开一个模态并开始滚动,然后当您到达末尾并继续滚动时,模态下方的内容(主体元素)会滚动?这称为滚动链接

在过去的几年里,有一些技巧可以使这项工作发挥作用,但是现在,由于‌overscroll-behaviorCSS 属性,我们只能使用 CSS 来做到这一点。

在下图中,您会看到默认的滚动链接行为。

为了提前避免这种情况,我们可以将其添加到任何需要滚动的组件(例如:聊天组件、移动菜单等)。这个属性的好处是它在滚动之前不会产生影响。

.modal__content {
    overscroll-behavior-y: contain;
    overflow-y: auto;
}

如果您想了解更多信息,我写了一篇详细的文章

CSS 变量的默认值(CSS Variable Fallback)

CSS 变量在网页设计中得到越来越多的使用。我们可以应用一种方法以不破坏体验的方式使用它们,以防 CSS 变量值由于某种原因为空。

这在通过 Javascript 提供 CSS 变量的值时特别有用。下面是一个例子:

.message__bubble {
    max-width: calc(100% - var(--actions-width));
}

该变量--actions-width正在calc()函数中使用,其值来自 Javascript。让我们假设 Javascript 由于某种原因失败了,会发生什么?在max-width将计算到none

我们可以提前避免这种情况,并向var().

.message__bubble {
    max-width: calc(100% - var(--actions-width, 70px));
}

这样,如果未定义变量,则将使用回退 ( 70px)。如果变量可能失败(例如:来自 Javascript),则可以使用此方法。否则,它是不需要的。

使用固定宽度或高度(Using fixed width or height)

破坏布局的常见事情之一是对具有不同长度内容的元素使用固定宽度或高度。

固定高度(The fixed height)

我经常看到一个固定高度的部分和大于该高度的内容,这导致布局损坏。不知道这看起来如何?

.hero {
    height: 350px;
}

为了避免内容溢出,我们需要使用 min-height 代替 height

.hero {
    min-height: 350px;
}

这样,如果内容变大,布局就不会中断。

固定宽度(The fixed with)

您是否见过标签离左右边缘太近的按钮?这是由于使用了固定宽度。

.button {
    width: 100px;
}

如果按钮的标签长于100px,它将靠近边缘。如果太长,文本会漏出来。这不好!

为了解决这个问题,我们可以简单地替换widthmin-width.

.button {
    min-width: 100px;
}

忘记 Background-Repeat(Forgetting background-repeat

通常,当使用一张大图作为背景时,我们往往会忘记考虑在大屏幕上查看设计的情况。默认情况下,该背景将重复

这在笔记本电脑屏幕上通常看不到,但在较大的屏幕上可以清楚地看到。

为了提前避免这种行为,请确保重置background-repeat

.hero {
    background-image: url('..');
    background-repeat: no-repeat;
}

使用 Justify-Content: Space-Between(Using justify-content: space-between)

在 flex 容器中,您可能会使用justify-content将子项彼此隔开。使用一定数量的子项,布局看起来不错。但是,当它们增加或减少时,布局会看起来很奇怪。

考虑以下示例。

我们有一个包含四个项目的 flex 容器。每个项目之间的间距不是gapmargin,它在那里是因为容器有justify-content: space-between.

.wrapper {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
}

当项目数少于四个时,将发生以下情况。

这不好。对此有不同的解决方案:

  • margin
  • flex gap(谨慎使用)
  • Padding(可以应用于每个子元素的父元素)
  • 添加空元素作为间隔。

为简单起见,我将使用gap.

.wrapper {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
}

小心使用 CSS 网格中的固定值(Be careful with fixed values in a CSS grid)

假设我们有一个包含 aside 和 main 的网格。CSS 看起来像这样:

.wrapper {
    display: grid;
    grid-template-columns: 250px 1fr;
    gap: 1rem;
}

由于空间不足,这将在小视口尺寸上中断。为避免此类问题,请在使用上述 CSS 网格时始终使用媒体查询。

@media (min-width: 600px) {
    .wrapper {
        display: grid;
        grid-template-columns: 250px 1fr;
        gap: 1rem;
    }
}

滚动条槽(Scrollbar Gutter)

与滚动相关的另一件事是scrollbar gutter。以前面的例子为例,当内容变长时,添加滚动条会导致布局偏移,发生布局偏移的原因是为滚动条占用布局宽度。

考虑下图。

请注意当内容因显示滚动条而变长时如何移动。我们可以通过使用scrollbar-gutter属性来避免这种行为,滚动条不占用布局宽度,可以理解为滚动条槽变为浮动。

.element {
    scrollbar-gutter: stable;
}

兼容不太好,作为增强属性可以:

CSS Flexbox 中的最小内容大小(Minimum content size in CSS flexbox)

下面的例子省略了细节,可以看这个我讲的案例 让 overflow-wrap 失效的 flex-wrap,更好理解。

如果一个 flex 项目有一个文本元素或一个比项目本身长的图像,浏览器不会缩小它们。这是 flexbox 的默认行为。

考虑以下示例。

.card {
    display: flex;
}

当标题有一个很长的词时,它不会换行。

即使我们使用overflow-wrap: break-word,它也不起作用。

.card__title {
    overflow-wrap: break-word;
}

要更改该默认行为,我们需要min-width将 flex 项的 设置为0。那是因为min-width默认值是auto,就会发生溢出。

.card__title {
    overflow-wrap: break-word;
    min-width: 0;
}

同样的事情适用于列 flex 包装器,但我们将使用它min-height: 0