- 原文地址:What is Modular CSS?
- 原文做者:Scott Vandehey
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:ssshooter
- 校对者:Hopsken Park-ma
模块化 CSS 是一组编写代码的原则,基于这个原则编写的代码具备高性能和可维护性。它起源于雅虎和 Yandex 的开发人员,目的是迎接维护大型代码库带来的挑战。有些规则在提出之初稍有争议,但后来被认为是最佳实践。css
目录:html
(偷偷告诉你:若是你对这篇文章的篇幅感到不知所措,观看视频可能更适合你,这篇文章来源于此演讲。)前端
模块化 CSS 使用的主要场景是棘手的大规模 CSS。正如 Nicholas Gallagher 所说的:android
来源:Nicholas Gallagher,图:dotCSSios
这句话直指大规模 CSS 问题的核心。写代码并不难,难的是在不让你的代码随着时间的推移成为拖累你的“技术债”。git
如下是 CSS Guidelines 中的一个示例,这个示例展现了一个问题:除了写这段代码的人,没有人知道这段代码是干什么的。github
<div class="box profile pro-user">
<img class="avatar image" />
<p class="bio">...</p>
</div>
复制代码
box
和profile
有什么关系?profile
和avatar
有什么关系?或者他们之间真的有关系吗?你应该在bio
旁边添加pro-user
吗?image
和profile
写在同一部分 CSS 吗?能够在其余地方使用avatar
吗?web
光看代码没法回答这些问题,你必须在 CSS 代码中推理他们的做用。编程
复用代码会很是棘手。假设你要在另外一个页面上复用某个页面上的样式,但你想这么作的时候,会发现那个样式是专为第一个页面而写的。代码的做者认为它只用在某个特定元素中,或者它是从页面继承某些类,在其余环境中根本不起做用。你不想修改原来的内容,而后直接复制了代码。后端
如今你有两个问题:一份原始代码,一份重复代码,你的维护负担直接增长了一倍。
大规模的 CSS 也难以维护。你改变了一个标签,样式就会像纸牌屋同样崩溃。你想更新一个页面上的样式,却破坏了另外一个页面的样式。你试图覆盖其余页面,但又深陷于优先度问题。
它让我想起了我最喜欢的 CSS 笑话之一:
那么咱们如何解决这些问题呢?答案在于模块化这个概念,但这是什么呢?咱们先看看 Harry Roberts 对关注点分离的看法:
来源:Harry Roberts,图:CSSwizardry.com
这是一个常见编程习惯,可是许多 CSS 开发者不太熟悉。这个思想是确保你所写的东西不会比你想要作的更多。
举个例子,说明我在学习模块化 CSS 以前的工做方式。设计师给我这样的草图:
图:Yandex
我会以为:“好吧,这是一个书店页面,侧边栏中有一些小部件,右侧列出了大概是书籍封面的清单,一个精选书评,下面还有其余的评论。”
我当时认为一个页面是一个完整的单元,页面里的较小部分从属于页面。这是一种自上而下的思考方法,这致使大量只服务于单个页面的一次性代码,不利于编写可复用代码。
图:Yandex
模块化 CSS 须要你换一个角度看问题,不从页面级别考虑,而是关注组成页面的小块。这不是一个页面而是一个组件的集合。
你会发现页面里包含的是 logo,搜索栏,导航,照片列表,辅助导航,标签框,视频播放器等。这些是能够网站的任何位置均可以独立使用的内容。它们只是碰巧在这个特定页面以这种方式组合。
模块化 CSS 是自下而上的思惟,须要从构建整个站点的可复用构建模块开始。
这会让你想起乐高?应该的!几乎全部撰写有关模块化 CSS 的人都使用乐高进行类比。使用标准化,易于理解,而且不依赖上下文的块来构建 UI 的是一个很好的思路。
这样的“块”最著名的例子之一是由 Nicole Sullivan 定义的“媒体对象”,她认为这种对象是你将在任何网站上找到的最小的组件之一。
它将固定宽度的图像组合到灵活宽度的容器的一侧,如今处处均可以看到这个模式。她撰写了一篇名为 The Media Object Saves Hundreds of Lines of Code 的案例研究,谈到将此模式应用于大型网站,最大的例子之一即是 Facebook:
这里高亮显示了 Facebook 流中的全部媒体对象。左上角我的信息,右侧导航元素,订阅的每一个帖子,甚至是广告都是媒体对象。有时它们彼此嵌套。虽然使用目的不一样,但它们都共享相同的基础模式:固定宽度的图像,弹性宽度的文本。
她的观点是,以 Facebook 的规模运营时,媒体对象就不止几十个,这样的页面上有数百上千个。所以,能够想象若是为复用样式做优化,能够节省大量代码,这能够带来真正的高性能和低成本。
那么,既然咱们已经明确了模块化的概念,那么让咱们看看这些年来推崇这一律念的三大框架:
面向对象的 CSS(Object-Oriented CSS)/ OOCSS 是模块化 CSS 的起源,由 Nicole Sullivan 于 2009 年提出,这基于她在雅虎的工做。这个框架的核心思想是 —— 对象是可重用的模式(pattern),其视觉外观不禁上下文决定。
来源:Nicole Sullivan,图:John Morrison
正如她在 2009 年所定义的那样,这就是模块化 CSS 的起源。除此以外,OOCSS 可归结为几个核心原则:
首先,不管你把它放在哪里,一个对象都应该看起来无差异,不该根据对象的上下文设置对象的样式。
例如,不是将侧边栏中的全部按钮都设置为橙色,将主区域中的全部按钮设置为蓝色,而是应该建立一个蓝色的按钮类,以及一个橙色的 modifier。这样作橙色按钮能够在任何地方使用,它们没有被绑定在侧边栏上,它们只是你的按钮样式之一。
她谈到的另外一个概念,是如何从正在应用的皮肤中抽象出对象的结构。
咱们能够回到媒体对象的例子。它的样式与标签结构无关。有一个容器,一个固定宽度的图像和内容。你能够应用不一样的样式,可是不管样式如何改变,标签结构都是同样的。
她建议的其余方法是为常见的视觉模式建立可复用的类。她给出了一个例子,在 2009 年的亚马逊网站几乎全部的东西都有阴影,但由于它们由不一样设计师创做,因此类似却不相同。经过标准化这些元素阴影,能够优化代码并使网站更高效。
当时她提出了一个很是具备争议性的规则,但后来被广为接受:使用 class 来命名对象及其子元素,这样能够在不影响样式的状况下修改 HTML 标签。
她不但愿 CSS 由 HTML 标签来肯定,这样的话若是将标题从“h1”更改成“h4”,则没必要更新 CSS。不管选择哪一个标签,该标题应该有一个固有的 class。例如,你的导航应该相似于 .site-nav
而不是 #header ul
。
既然建议“老是使用 class”,那么天然禁止使用 ID 选择器。这与当时使用 ID 做为命名空间的常见实践相违背,直接引用嵌套在其中的元素。
ID 会扰乱 CSS 优先度,这是其次,对象必须是可复用的。根据定义,ID 是惟一的。所以,若是在对象上设置 ID,则没法在同一页面上重复使用它,缺乏了模块化对象的要点。
接下来介绍下一个弘扬模块化 CSS 精神的框架。BEM,三个字母分别表明 Block、Element、Modifier,BEM 也是在 2009 年提出,起源于 Yandex(能够说是俄语版的 Google),除搜索业务外还运营网络邮件程序,所以在编程上他们也须要解决与雅虎相同规模的难题。
他们提出了一套很是相似的代码原则。他们的核心概念是 —— 块(block)(Nicole 称之为“物体(object)”)由子元素(element)构成,而且能够修改(modified)(或“主题化”)。
如下是其中一位负责 BEM 的开发者 Varya Stepanova 对 BEM 的描述:
来源:Varya Stepanova,图:ScotlandJS
BEM 由 3 部分组成:
块是网页逻辑和功能的独立组件。BEM 的发起人对其提出了更详尽的定义:
首先,块是可嵌套的。它们应该能被包含在另外一个块中,而不会破坏任何样式。例如,可能在侧栏中有一个标签界面小部件的块,该块可能包含按钮,这些按钮也是一种单独的块。按钮的样式和选项卡式元素的样式不会相互影响,一个嵌套在另外一个中,仅此而已。
其次,块是可重复的。界面应该可以包含同一块的多个实例。就像 Nicole 所说的媒体对象同样,复用块能够节省大量代码。
元素是块的组成部分,它不能在块以外使用。一个不错的例子:一个导航菜单,它包含的项目在菜单的上下文以外没有意义。你不会为菜单项定义块,菜单自己应定义为块,而菜单项是其子元素。
修饰符定义块的外观和行为。例如,菜单块的外观的垂直或水平,取决于所使用的修饰符。
BEM 所作的另外一件事是定义了很是严格的命名约定:
.block-name__element--modifier
这看起来有点复杂,我来分解一下:
-
)分隔__
)分隔--
)分隔这么说也有点抽象,举一个例子:
如今咱们有一个标准的乐高 minifig。他是一个蓝色的宇航员。咱们将使用 .minifig
类来区分他。
能够看到 .minifig
块由较小的元素组成,例如 .minifig__head
和 .minifig__legs
。如今咱们添加一个修饰符:
经过添加 .minifig--red
修饰符,咱们建立了标准蓝色宇航员的红色版本。
或者,咱们可使用 .minifig--yellow-new
修饰符将咱们的宇航员改成新式黄制服版。
你可使用一样的方式进行更夸张的修改。经过使用 .minifig--batman
修饰符,咱们只用一个类就改变了 minifig 的每一个部分的外观。
这是实践中的 BEM 语法例子:
<button class="btn btn--big btn--orange">
<span class="btn__price">$9.99</span>
<span class="btn__text">Subscribe</span>
</button>
复制代码
即便不看样式代码,你也能够一眼就看出这段代码会建立一个既大又橙的价格按钮。不管你是否喜欢带有连字符和下划线的这种风格,拥有严格的命名约定是模块化 CSS 向前迈出的一大步,这让代码带有自文档的效果!
就像 OOCSS 建议使用 class 而不使用 ID 同样,BEM 也为代码风格做了一些限制。最值得注意的是,他们认为不该该嵌套 CSS 选择器。嵌套选择器扰乱了优先度,使得重用代码变得更加困难。例如,只需使用 .btn__price
而不是 .btn .btn__price
。
注意:这里的嵌套指实践中在 Sass 或 Less 嵌套选择器的作法,但即便你没有使用预处理器也适用,由于这关乎选择器优先度问题。
这个原则不出问题是由于严格的命名约定。咱们曾经使用嵌套选择器将它们隔离在命名空间的上下文中。而 BEM 的命名约定自己就提供了命名空间,所以咱们再也不须要嵌套。即便 CSS 的根级别的全部内容都是单个类,但这些名称的具体程度足以免冲突。
通常来讲,选择器能够在没有嵌套的状况下生效,就不要嵌套它。 BEM 容许此规则的惟一例外是基于块状态或其修饰符的样式元素。例如,可使用 .btn__text
而后用 .btn--orange .btn__text
来覆盖应用了修饰符按钮的文本颜色。
咱们最后要讨论的框架是 SMACSS,含义是 CSS 的可扩展性和模块化架构(Scalable & Modular Architecture)。Jonathan Snook 于 2011 年提出了 SMACSS,当时他在雅虎工做,为 Yahoo Mail 编写 CSS。
来源:Jonathan Snook,图:Elida Arrizza
他在 OOCSS 和 BEM 的基础上添加的关键概念是,不一样类别的组件须要以不一样的方式处理。
如下是他为 CSS 系统可能包含的规则定义的类别:
下一个原则是使用前缀来区分类别,他喜欢 BEM 明确的命名约定,但他还但愿可以一目了然地看出模块的类型。
l-
用做布局规则的前缀:l-inline
m-
用做模块规则的前缀:m-callout
is-
用做状态规则的前缀:is-collapsed
(基础规则没有前缀,由于它们直接应用于 HTML 元素而不使用类。)
这些框架的相同之处远胜于其不一样之处。我看到从 OOCSS 到 BEM 再到 SMACSS 的明确发展。它们的发展表明了咱们行业在性能和大规模 CSS 领域不断增加的经验。
你没必要选择其中一个框架,相反,咱们能够尝试定义模块化 CSS 的通用规则。让咱们看看这些框架共用和保留的最佳部分。
模块化系统由如下元素组成:
模块化系统中的样式能够分为如下几类:
a
、li
和 h1
.l-centered
、.l-grid
和 .l-fixed-top
.m-profile
、.m-card
和 .m-modal
.is-hidden
、.is-collapsed
和 .is-active
.h-uppercase
、.h-nowrap
和 .h-muted
在模块化系统中编写样式时,请遵循如下规则:
模块化 CSS 最多见的反对意见就是,它会在 HTML 中产生许多类。我认为这是由于长期以来 CSS 的最佳实践都认为应该避免大量 class 使用。早在 2011 年,Nicole Sullivan 就写了一篇很棒的博文 Our (CSS) Best Practices are Killing Us,明确驳斥了这个想法。
我看到一些开发人员提倡使用预处理器的 extend
函数将多个样式链接成一个类名。我建议不要这样作,由于它会使你的代码不那么灵活。他们不能让其余开发者以新的方式组合你的乐高积木,而是固定了你定义的几种组合。
不要由于类名太长而惧怕,他们是自文档的!当我看到 BEM 风格的类名(或任何其余模块化命名约定)时,我会以为很愉悦,由于只要看一眼就能知道这些类的含义。你能够在 HTML 中清晰理解它们。
长话短说:没这回事。
模块化 CSS 初学者能够快速掌握子元素的概念:minifig__arm
是 minifig
的一部分。然而,有时候他们处理 CSS 中的 DOM 结构时,会疑问如何做深层嵌套,好比 minifig__arm__hand
。
没有必要这样作。请记住,这个思路是要将样式与标记分离。不管 hand
是 minifig
的直接子元素仍是嵌套了多少层,都可有可无。CSS 关心的只有 hand
是 minifig
的孩子。
.minifig {}
.minifig__arm {}
.minifig__arm__hand {} /* don't do this */ .minifig__hand {} /* do this instead */ 复制代码
模块化 CSS 初学者比较关注的另外一件事是模块之间的冲突。例如,若是我将 l-card
模块和 m-author-profile
模块同时应用于同一个元素,是否会致使问题?
答案是:理想状况下,模块不该该重叠太多。在这个例子中,l-card
模块关注布局,而 m-author-profile
模块关注样式,你可能会看到 l-card
设置宽度和边距,而 m-author-profile
设置背景颜色和字体。
测试模块是否冲突的一种方法是以随机顺序加载它们。你能够将项目构建配置中设定为在构建时随机交换样式位置。若是看到bug,就证实你的 CSS 须要以特定顺序加载。
若是你发现须要将两个模块应用于同一个元素而且它们存在冲突,请考虑它们是否真的是两个独立的模块。也许它们能够用一个修饰符组合成一个模块?
该规则的最后一个例外是“helper”或“utility”类可能会发生冲突,在这些状况下,你能够安全地考虑使用 !important
。我知道,你曾被告知 !important
不是什么好东西,永远不该该被使用,但咱们的作法有细微的差异:主动使用它来确保 helper 类老是优先仍是不错的。 (Harry Roberts has more to say on this topic in the CSS Guidelines。)
咱们来简要回顾一下,还记得这段代码吗?
<div class="box profile pro-user">
<img class="avatar image" />
<p class="bio">...</p>
</div>
复制代码
box
和profile
有什么关系?profile
和avatar
有什么关系?或者他们之间有关系吗?你应该在bio
旁边添加pro-user
吗?image
和profile
写在同一部分 CSS 吗?能够在其余地方使用avatar
吗?
如今咱们知道如何解决这些问题了。经过编写模块化 CSS 并使用适当的命名约定,咱们能够编写自文档的代码:
<div class="l-box m-profile m-profile--is-pro-user">
<img class="m-avatar m-profile__image" />
<p class="m-profile__bio">...</p>
</div>
复制代码
咱们能够看到哪些类彼此相关,哪些类彼此不相关,以及如何相关。咱们知道在这个组件的范围以外咱们不能使用哪些类,固然,咱们还知道哪些类能够在其余地方复用。
模块化 CSS 简化了代码并推动了重构,产出自文档的代码,这样的代码不影响外部做用域且可复用。
或者换句话说,模块化 CSS 是可预测的,可维护的而且是高性能的。
如今咱们能够重温那个老笑话,结局发生了变化:
若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。