CSS 编码技巧

最近,我们看到很多从初学者到经验丰富的开发人员都在为 CSS 而苦恼。有些人不喜欢 CSS 的工作方式,并想知道用其他语言代替 CSS 是否更好——CSS 处理器就是从这种想法中诞生的。有些人使用 CSS 框架,希望他们能够编写更少的代码(我们在 之前的一篇文章 中已经看到过,为什么这种情况通常并非如此)。有些人开始完全放弃 CSS,而使用 JavaScript 来应用样式。

但是你并不总是需要在你的工作流程中包含 CSS 处理器。你不需要将臃肿的框架作为任何项目的默认起点。而且使用 JavaScript 来完成 CSS 应该做的事情仅仅是一个糟糕的想法。

在这篇文章中,我们将看到一些编写更易于维护的 CSS 代码的技巧和建议,这样你的样式表就会更短,并且规则更少。CSS 可以成为一个方便的工具,而不是负担。

“最小的可行选择器”

CSS 是一种声明式语言,你可以在其中指定规则,这些规则将为 DOM 中的元素设置样式。在这门语言中,有些规则在应用顺序中具有优先级,例如内联样式会覆盖某些先前的规则。

例如,如果我们有以下 HTML 和 CSS 代码

<button class="button-warning">
.button-warning {
  background: red;
}

button, input[type=submit] {
  background: gray;
}

尽管 .button-warningbutton, input[type=submit] 规则之前定义,但它将覆盖后者的 background 属性。为什么?决定哪个规则会覆盖另一个规则的样式的标准是什么?

特异性.

有些选择器被认为比其他选择器更具特异性:例如,#id 选择器会覆盖 .class 选择器。

如果我们使用了一个比实际需要的更具特异性的选择器会怎样?如果我们以后想要覆盖这些样式,我们需要一个更具特异性的选择器。如果我们以后需要覆盖这个更具特异性的选择器,我们需要…是的,这是一个不断增长的雪球,最终会变得非常难以维护。

因此,无论何时编写选择器,都要问自己:这是这里可以完成工作的最不具特异性的选择器吗?

所有特异性规则都在 W3C CSS 选择器规范 中正式定义,这是了解 CSS 选择器所有细节的方法。为了更容易理解,可以阅读 关于 CSS 特异性的这篇文章

不要用新规则来解决错误

让我们想象一下这种情况:你的 CSS 中有一个错误,并且你找到了哪个 DOM 元素具有错误的样式。并且你意识到它以某种方式继承了一个它不应该具有的属性。

不要只是用更多的 CSS 来解决它。如果你这样做,你的代码库会变得更大,并且定位未来的错误会更难。

相反,停下来,退一步,使用浏览器中的开发者工具来检查元素并查看整个级联。准确地确定哪个规则正在应用你不需要的样式。并修改该现有规则,使其不再具有意外的后果。

在 Firefox 中,你可以通过右键单击页面上的元素并选择 检查元素 来调试级联。

image00

看看所有这些荣耀的级联。在这里,你可以看到应用于元素的所有规则,以及它们的应用顺序。最顶部的条目是那些具有更高特异性的条目,并且可以覆盖先前的样式。你可以看到一些规则中有一些属性被删除了:这意味着一个更具特异性的规则正在覆盖该属性。

并且你不仅可以看到规则,还可以实时地打开和关闭它们,或者更改它们并观察结果。它非常适合修复错误!

所需的修复可能是一个规则更改,也可能是在级联中的不同位置进行规则更改。修复可能需要一个新规则。至少你会知道这是一个正确的选择,并且是你的代码库需要的。

这也是寻找重构机会的好时机。尽管 CSS 不是一种编程语言,但它也是源代码,你应该像对待 JavaScript 或 Python 一样对待它:它应该简洁、可读,并在需要时进行重构。

不要使用 !important

之前的建议已经暗示了这一点,但由于它至关重要,我想强调一下:不要在你的代码中使用 !important

!important 是 CSS 中的一个功能,它允许你打破级联。CSS 代表“层叠样式表”,这是一个提示。

!important 通常在你匆忙修复错误并且没有时间或意愿修复你的级联时使用。当你在包含 具有非常特异性规则的 CSS 框架 时,它也被广泛使用,并且覆盖它们太难了。

当你将 !important 添加到一个属性时,浏览器会忽略其他具有更高特异性的规则。当你要!important 一个规则来覆盖另一个被标记为 !important 的规则时,你就知道你真的遇到了麻烦。

!important 只有一个合法用途——在使用开发者工具调试某些东西时。有时你需要找出哪个属性的值可以修复你的错误。在开发者工具中使用 !important 并实时修改 CSS 规则,可以让你在忽略级联的情况下找到这些值。

一旦你知道了哪些 CSS 部分将起作用,你就可以回到你的代码中,看看你想在级联的哪个位置包含这些 CSS 部分。

除了 px 和 % 之外还有其他方法

使用 px(像素)和 %(百分比)单位非常直观,因此我们将在这里重点关注不太为人所知或不太直观的单位。

Emrem

最著名的相对单位是 em。1em 等效于该元素的字体大小。

让我们想象一下以下 HTML 部分

<article>
  <h1>Title</h1>
  <p>One Ring to bring them all and in the darkness bind the.</p>
</article>

以及一个只有以下规则的样式表

article {
  font-size: 1.25em;
}

大多数浏览器默认情况下将 16 像素的基线字体大小应用于根元素(顺便说一下,这可以通过用户覆盖——这是一个很好的可访问性功能)。因此,该文章元素的段落文本可能会以 20 像素(16 * 1.25)的 font-size 进行渲染。

h1 怎么办?为了更好地理解会发生什么,让我们将以下其他 CSS 规则添加到样式表中

h1 {
  font-size: 1.25em;
}

尽管它也是 1.25em,与 article 相同,但我们必须考虑到 em 单位是累积的。也就是说,例如,h1 作为 body 的直接子级,将具有 20 像素(16 * 1.25)的 font-size。但是,我们的 h1 位于一个与根元素(我们的 article)具有不同 font-size 的元素内。在这种情况下,1.25 指的是级联给出的 font-size,因此 h1 将以 25 像素(16 * 1.25 * 1.25)的 font-size 进行渲染。

顺便说一下,与其在脑子里进行所有这些乘法链,不如直接使用 检查器 中的 计算 选项卡,它将以像素显示实际的最终值

image01

em 单位非常灵活,可以非常轻松地更改(甚至动态地更改)页面的所有大小(不仅是 font-size,还有其他属性,如 line-heightwidth)。

如果你喜欢 em 的“相对于基线大小”部分,但不喜欢累积部分,可以使用 rem 单位rem 单位就像 忽略累积并只使用根元素大小的 em

因此,如果我们使用我们之前的 CSS,并将 h1 中的 em 单位更改为 rem

article { font-size: 1.25em; }
h1 { font-size: 1.25rem; }

所有 h1 元素都将具有 20 像素(假设 16px 基线大小)的计算 font-size,无论它们是否位于文章内。

vw 和 vh

vwvh 是视窗单位。 1vw 是视窗宽度的 1%,而 1vh 是视窗高度的 1%。

如果你需要一个需要占据整个屏幕的 UI 元素(比如模态对话框的典型半透明深色背景),它们非常有用,这些元素并不总是与实际的 body 大小相关。

其他单位

还有其他单位可能不像以前那么常见或通用,但你不可避免地会遇到它们。你可以在 MDN 上 了解更多关于它们的信息。

使用 flexbox

我们在关于 CSS 框架的 之前的一篇文章 中谈到了这一点,但 flexbox 模块简化了制作布局和/或对齐事物的任务。如果你不熟悉 flexbox,请查看 这个入门指南

而且是的,你今天就可以使用 flexbox。除非你出于商业原因确实需要支持旧版本的浏览器。目前的 浏览器对 flexbox 的支持率超过 94%。因此你可以停止编写所有那些难以调试和维护的浮动 div

此外,请关注即将发布的 Grid 模块,它将使布局实现变得轻而易举。

使用 CSS 处理器时…

Sass 或 Less 等 CSS 编译器在前端开发领域非常流行。它们是强大的工具,可以让我们更高效地使用 CSS。

不要滥用选择器嵌套

这些处理器或“编译器”的一个常见功能是选择器嵌套。例如,以下 Less 代码

a {
  text-decoration: none;
  color: blue;

  &.important {
    font-weight: bold;
  }
}

将被转换为以下 CSS 规则

a {
  text-decoration: none;
  color: blue;
}

a.important {
  font-weight: bold;
}

此功能允许我们编写更少的代码,并对影响 DOM 树中通常一起出现的元素的规则进行分组。这对于调试很有用。

但是,滥用此功能也很常见,最终会导致在 CSS 选择器中复制整个 DOM。因此,如果我们有以下 HTML

<article class="post">
  <header>
    <!-- … -->
    <p>Tags: <a href="..." class="tag">irrelevant</a></p>
  </header>
  <!-- … -->
</article>

我们可能会在 CSS 样式表中找到以下内容

article.post {
  // ... other styling here
  header {
    // ...
    p {
      // ...
      a.tag {
        background: #ff0;
      }
    }
  }
}

主要缺点是这些 CSS 规则具有极其具体的选择器。我们已经看到,这是我们应该避免的事情。过度嵌套还有其他缺点,我在另一篇文章中对此进行了讨论。

简而言之:不要让嵌套生成你不会自己键入的 CSS 规则。

包含与扩展

CSS 处理器的另一个有用功能是混合器,它们是可重复使用的 CSS 代码块。例如,假设我们要为按钮设置样式,并且大多数按钮都具有一些基本 CSS 属性。我们可以在 Less 中创建一个像这样的混合器

.button-base() {
  padding: 1em;
  border: 0;
}

然后创建一个像这样的规则

.button-primary {
  .button-base();
  background: blue;
}

这将生成以下 CSS

.button-primary {
  padding: 1em;
  border: 0;
  background: blue;
}

如您所见,重构公共代码非常方便!

除了“包含”混合器之外,还可以选择“扩展”或“继承”它(确切的术语因工具而异)。这样做会将同一规则中的多个选择器组合在一起。

让我们看一个使用之前.button-base混合器的示例

.button-primary {
  &:extend(.button-base)
  background: blue;
}

.button-danger {
  &:extend(.button-base)
  background: red;
}

这将被转换为

.button-primary, .button-danger {
  padding: 1em;
  border: 0;
}

.button-primary { background: blue; }
.button-danger { background: red; }

网上有一些文章告诉我们只使用“包含”,而另一些文章告诉我们只使用“扩展”。事实是它们会生成不同的 CSS,它们都不是天生错误的,并且根据您的实际情况,使用其中一个或另一个会更好。

如何选择它们?同样,适用“我会手动编写这些代码吗?”的经验法则。


我希望这可以帮助您反思您的 CSS 代码并编写更好的规则。请记住我们之前所说的:CSS 代码,因此与您代码库中的其他部分一样,值得同样程度的关注和呵护。如果你给予它一些关爱,你将获得回报。

关于 Belén Albeza

Belén 是一位在 Mozilla 开发者关系部门工作的工程师和游戏开发者。她关心网络标准、高质量代码、可访问性和游戏开发。

更多 Belén Albeza 的文章…


25条评论

  1. awal

    > 如果我们使用的选择器比实际需要的更具体,会发生什么?如果我们以后想要覆盖这些样式,我们需要一个更具体的选择器。如果我们以后需要覆盖这个更具体的选择器,我们将需要……是的,这是一个越来越大的雪球,最终将变得非常难以维护。

    这只是硬币的一面。另一面是

    假设我们需要一个具有最小特定性的选择器。现在假设我们向要设置样式的 DOM 添加了一些新元素。我们的选择器也会选择新元素,这不是我们用例中想要发生的事情。因此,我们增加了选择器的特定性,或者在要定位的 DOM 元素中添加了一个额外的类。前者为 CSS 带来了您所描述的雪球效应,而后者则为 DOM 带来了雪球效应。

    现在您可以(合理地)说,应该首先准备一个具有语义意义的明确 DOM 结构,然后*再*进行样式设置,但请记住,您经常需要更改 DOM 结构来适应一些 CSS 限制*同时*进行样式设置(例如,无法根据子选择器选择和设置父元素的样式,因此您需要将事物包装在另一个 div 中)。当然,在现实世界中,您的经理并不关心您的 DOM/CSS 结构。他只想要在按钮组中添加一个按钮:)

    另一方面,如果您有一种技术也可以解决这个问题,我很乐意听取!

    2016年5月18日 10:05

    1. Belén Albeza

      当然,在现实世界中,您的经理并不关心您的 DOM/CSS 结构。他只想要在按钮组中添加一个按钮:)

      在现实世界中,您的经理关心您修复错误所花费的时间。如果由于您甚至无法理解自己的代码而花费了几个小时,她会注意到的。

      所以“这需要尽快完成”并不是一直编写糟糕代码的借口。

      另一方面,如果您有一种技术也可以解决这个问题,我很乐意听取!

      很明显,如果您向某件事添加更多功能(在本例中是更多 UI),您可能需要更多代码。您添加了多少新代码是这里关键的问题。

      如何保持代码库合理?通过关心、执行代码审查、重构。

      除此之外,我认为在大型、复杂、不断变化的项目中,让 CSS 变得可控的明智方法是拥有一个始终保持最新状态的 CSS 样式指南

      2016年5月19日 02:02

      1. Jon

        我发现低特定性选择器是 CSS 错误的最大原因,也是最难修复的原因。这些低特定性规则等同于全局变量,更改它们非常冒险,因为您不知道它们还会影响什么。而且由于它们没有被精确地定位,因此新的 HTML 很容易意外地拾取不需要的样式,因为您重复使用了“article”或“navigation”之类的类名,并且有一个低特定性规则适用于它。

        因此,许多项目积累了大量的低特定性实用程序类,没有人有信心更改或删除它们,因为这样做可能会破坏他们不知道的其他东西。正是这些低特定性规则导致人们在规则上叠加更多规则和 !important 覆盖。

        相反,UI 应该被设计为一系列组件,每个组件都具有应用于它们的特定规则集,并且只应用于它们。全局低特定性规则应该保持在绝对最小限度,只将常用的字体和颜色应用于基本 HTML 元素。其他所有规则都应与它们设置样式的组件的 DOM 结构紧密相关,并且要足够具体以避免影响其他任何东西。

        通过以这种方式编写 CSS,样式会得到适当的封装,开发人员可以放心地对其进行更改,因为它们会确切地知道它们将影响什么以及不会影响什么。当一个组件被删除时,与其相关的样式也可以安全地删除,并且通过将样式与组件 HTML 放在一起,很容易同时处理两者,开发人员将确切地知道代码库中的哪个位置可以解决针对特定组件报告的错误。

        这种特定的、目标明确的、分离的规则还有助于协作开发。如果我是唯一一个处理与特定组件相关的工单的开发人员,我可以安全地更改其样式,因为我知道当我提交更改时,我不会出现合并问题,因为其他人在同一时间处理相同的 CSS 文件。

        2016年5月20日 05:24

        1. awal

          > 这些低特定性规则等同于全局变量,更改它们非常冒险,因为您不知道它们还会影响什么。

          没错。我认为这是一个很好的“类比”。问题是,虽然全局变量在几乎所有编程语言中都有一个简单的替代方案(函数范围/局部变量),但 CSS/HTML 没有这些。主要选择目标 - 类名和 id 是全局范围的。

          但这并没有否定文章中提出的非常精辟的观察,即高度具体的规则也会导致问题。这就是为什么我说硬币有两面。你试图让一侧保持正常,而另一侧却变得混乱。

          解决方案显然是把责任归咎于语言;)

          2016年5月21日 08:32

      2. Anon

        “在现实世界中,您的经理关心您修复错误所花费的时间。如果由于您甚至无法理解自己的代码而花费了几个小时,她会注意到的。”

        我一生中见过的独角兽比这种经理还多。别辞职!

        2016年6月7日 06:20

      3. Sam

        有趣的一点观察:这个评论系统本身的 CSS 有一条规则.comment.bypostauthor .comment__title::after,它不够具体,给 Belén 的评论的所有回复都加上了“作者”标签!

        2016年6月9日 08:58

  2. Nils

    在我看来,Flexbox 还没有准备好。是的,支持率为 94%,但这其中也包括有缺陷的实现或旧标准的实现(尤其是 IE)。只有 76% 的浏览器正确地实现了它,这使得在支持 IE 时使用它变得很麻烦。

    2016年5月19日 06:20

    1. Konstantin

      完全同意 @Nils 的说法,我也认为现在还不是在生产代码中使用 Flexbox 的时候。

      2016年6月6日 02:06

    2. Peter

      我倾向于同意。当我读到“Flexbox 已经可以使用了”时,我总是很好奇自己错过了什么。对 IE10/11、Android 浏览器和 iOS Safari 的部分支持意味着,如果我选择走 Flexbox 路线,我仍然需要构建一个可靠的备用布局。

      2016年6月8日 03:10

  3. Gerd Neumann

    很棒的文章!

    规则的特定性总是困扰着我。如果开发工具能够在某处显示规则的计算特定性,那就太好了……前段时间我用 Google 搜索过它,我没有找到任何工具(使用任何浏览器)可以让我知道这条规则是 0,1,2,2,但这只是 0,2,0,0。所以最终你需要自己“计算”它,这很难,因为作为一个新手,你并不知道整个规范。

    2016年5月20日 01:33

    1. Belén Albeza

      嗨 Gerd,感谢您的反馈!我会把它传达出去的。

      2016年5月20日 06:21

      1. Gerd Neumann

        谢谢,Belén!如果这最终成为一个 Bugzilla 报告,请在这里分享,因为我想跟踪它的状态。

        顺便说一句,您的文章刚刚在 Hacker News 上排名第一:https://news.ycombinator.com/item?id=11736286 恭喜!

        2016年5月20日 06:37

        1. Janne Clarson

          给你,早在两年前就提交了
          https://bugzilla.mozilla.org/show_bug.cgi?id=977098

          2016年5月23日 04:22

    2. Ryan Johnson

      我发现了一个计算 CSS 特定性的实用工具。 https://specificity.keegan.st/

      2016 年 5 月 20 日 上午 11:45

  4. Gerd Neumann

    阅读所有关于使用开发工具调试 CSS 的内容,让我对它的 JS 调试器(仅仅是“只读”)感到非常难过。想象一下,如果我能在调试器中像在检查器中使用 CSS 一样启用/禁用、修改值和语句,那将是截然不同的世界。在调试器中,我只能“查看”当前状态,无法对任何内容进行动态更改以进行测试……

    2016 年 5 月 20 日 上午 1:51

  5. George Stephanis

    这在之前的建议中有所暗示,但由于这一点至关重要,我想强调一下:不要在你的代码中使用 `!important`。

    Ehhhh,有时 `!important` 是一个不错的选择。我同意,只是为了“快速修复”,这是一个糟糕的主意,但它可以很好地解决其他问题。

    例如:如果你正在编写一个插件或某种模块,它将在网站中注入一段带样式的内容,而你无法控制网站的正常样式,并且你需要避免意外地让网站覆盖你自己的样式,这是一个很好的解决方案。

    在这种情况下,它被用来确保你的块在成千上万甚至数百万个网站上都能正确显示,不会成为某些尴尬的意外本地 CSS 规则的受害者,比如 `#body #content #post strong {}`,有些开发者曾经认为这是一个聪明的想法,但没有意识到所赋予的疯狂的特定级别所带来的影响。(我见过这种情况,以及它带来的支持负担)。

    最好使用合理的选择器只针对你的标记,并使用 `!important`,这样网站就可以根据需要覆盖你的样式,但他们只需要明确地进行操作。

    简而言之,在所有情况下严格遵守这些规则会导致你的用户遇到比淡化这些规则并做出明智的决策(同时了解其影响)更糟糕的结果。

    2016 年 5 月 20 日 上午 2:53

  6. peter

    现在我们几乎所有的 HTML 代码都是以模块化方式编写的,我发现混合使用预定义的 CSS 类效果最好。例如,`class="inline-block bg-bbb text-333 padding-5-15"`

    2016 年 5 月 20 日 上午 8:50

    1. Jens

      听起来这是一个非常好的主意——不再需要 `style` 属性——你可能会对 universal.css 项目非常感兴趣,这是一个基于这个想法的伟大项目: https://github.com/marmelab/universal.css

      他们有预定义的类,比如 `border-top-width-1-dot-04em` 或 `margin-left-10px`,一定要看看他们常见问题解答中的最后一个问题;)

      2016 年 5 月 21 日 上午 1:57

    2. Greg Bell

      “class = bg-bbb text-333 padding-5-15”

      但是,我们不是又回到了将样式语义放入内容和布局中吗?

      2016 年 5 月 25 日 上午 4:29

  7. Johntrepreneur

    关于在团队中使用 LESS 嵌套的有趣之处在于,至少在我们的团队中,人们倾向于使用它来组织样式,以便于查找和定位。虽然这在编辑 LESS 文件时可能有效,但最终会导致巨大的、不必要的繁重的选择器,这显然不好。使用 LESS 的导入功能导入其他文件是解决 CSS 组织问题和防止嵌套导致繁重选择器的更好方法。IMO。

    2016 年 5 月 20 日 下午 1:08

  8. Jaydev Gajera

    我也认为 Flex 是创建响应式布局的好方法。我尝试创建一个 Sass 模块来创建基于 Flex 的响应式布局。
    这是 Github 仓库:https://github.com/jaydevonline/floatless
    欢迎随时提供反馈,请提出任何修改建议或指出我做错的地方。

    2016 年 5 月 22 日 下午 10:10

  9. Alex Walgreen

    在你的例子中,你写了“button-warning”。我经常看到这种情况。但是,还有其他更灵活的方法。

    你也可以写一些类似的东西

    .button {
    /* 在这里设置默认的按钮样式 */
    }

    .warning {
    background: red;
    color: black;
    }

    .info {
    background: blue;
    color: white;
    }

    然后在 HTML 中使用它

    在我看来,这也是一种干净且好的 CSS 写法,因为对于任何你想要使其看起来像按钮的元素,你只需应用 button 类。而且,对于任何你想要表示警告或信息的元素,你只需添加 warning 或 info 类。

    这允许更灵活地复用样式。假设你想要一个带有边框的特定按钮

    .bordered {
    border: 1px solid black;
    }

    所以我们创建了一个带有边框的按钮,就像

    假设我们现在想创建一个 DIV,为用户提供信息消息

    内容

    你对这种使用 CSS 的方式有什么看法?

    2016 年 5 月 24 日 上午 5:55

  10. Adrian Bettridge-Wiese

    关于 `@include` 和 `@extend`,你无法在媒体查询中使用 `@extend`,这会导致一些真正的问题。就我个人而言,我讨厌这种情况,但这是我们所处的世界,所以只能使用 `@include` 了!

    2016 年 5 月 24 日 上午 10:18

    1. Alan

      或者干脆停止使用预处理器。我这么做了,并且对纯 CSS 和一些通过 postcss 进行的增强非常满意。

      2016 年 5 月 28 日 上午 8:29

  11. feibinyang

    太好了!
    我能把它翻译成中文吗?它是非商业性的。

    2016 年 5 月 26 日 下午 8:22

本文的评论已关闭。