CSS 编码规范
在参与规模庞大、历时漫长且参与人数众多的项目时,所有开发者遵守如下规则极为重要:
- 保持 CSS 便于维护
- 保持代码清晰易懂
- 保持代码的可拓展性
为了实现这一目标,我们要采用诸多方法。
本文档第一部分将探讨语法、格式以及分析 CSS 结构;第二部分将围绕方法论、思维框架以及编写与规划 CSS 的态度。
CSS 文档分析
无论编写什么文档,我们都应当尽力维持统一的风格,包括统一的注释、统一的语法与统一的命名规范。
总则
尽量将行宽控制在 80 字节以下。渐变(gradient)相关的语法以及注释中的 URL 等可以算作例外,毕竟这部分我们也无能为力。
我倾向于用 4 个空格而非 Tab 缩进,并且将声明拆分成多行。
单一文件与多文件
有人喜欢在一份文件文件中编写所有的内容,而我在迁移至 Sass 之后开始将样式拆分成多个小文件。这都是很好的做法。无论你选择哪种,下文的规则都将适用,而且如果你遵守这些规则的话你也不会遇到什么问题。这两种写法的区别仅仅在于目录以及区块标题。
目录
在 CSS 的开头,我会写一份目录,例如:
/*------------------------------------*\
$CONTENTS
\*------------------------------------*/
/**
* CONTENTS............You’re reading it!
* RESET...............Set our reset defaults
* FONT-FACE...........Import brand font files
*/
这份目录可以告诉其他开发者这个文件中具体含有哪些内容。这份目录中的每一项都与其对应的区块标题相同。
如果你在维护一份单文件 CSS,对应的区块将也在同一文件中。如果你是在编写一组小文件,那么目录中的每一项应当对应相应的 @include 语句。
区块标题
目录应当对应区块的标题。请看如下示例:
/*------------------------------------*\
$RESET
\*------------------------------------*/
区块标题前缀 $
可以让我们使用 [Cmd|Ctrl]+F 命令查找标题名的同时将搜索范围限制在区块标题中。
如果你在维护一份大文件,那么在区块之间空 5 行,如下:
/*------------------------------------*\
$RESET
\*------------------------------------*/
[Our
reset
styles]
/*------------------------------------*\
$FONT-FACE
\*------------------------------------*/
在大文件中快速翻动时这些大块的空档有助于区分区块。
如果你在维护多份、以 @include 连接的 CSS 的话,在每份文件头加上标题即可,不必这样空行。
代码顺序
尽量按照特定顺序编写规则,这将确保你充分发挥 CSS 缩写中第一个 C 的意义:cascade,层叠。
一份规划良好的 CSS 应当按照如下排列:
- Reset 万物之根源
- 元素类型 没有 class 的
h1
、ul
等 - 对象以及抽象内容 最一般、最基础的设计模式
- 子元素 由对象延伸出来的所有拓展及其子元素
- 修补 针对异常状态
如此一来,当你依次编写 CSS 时,每个区块都可以自动继承在它之前区块的属性。这样就可以减少代码相互抵消的部分,减少某些特殊的问题,组成更理想的 CSS 结构。
关于这方面的更多信息,强烈推荐 Jonathan Snook 的 SMACSS。
CSS 规则集分析
[selector]{
[property]:[value];
[<- Declaration ->]
}
[选择器]{
[属性]:[值];
[<- 声明 ->]
}
编写 CSS 样式时,我习惯遵守这些规则:
- class 名称以连字符(-)连接,除了下文提到的 BEM 命名法;
- 声明拆分成多行;
- 声明以相关性顺序排列,而非字母顺序;
- 有前缀的声明适当缩进,从而对齐其值;
- 缩进样式从而反映 DOM;
- 保留最后一条声明结尾的分号。
例如:
.widget{
padding:10px;
border:1px solid #BADA55;
background-color:#C0FFEE;
-webkit-border-radius:4px;
-moz-border-radius:4px;
border-radius:4px;
}
.widget-heading{
font-size:1.5rem;
line-height:1;
font-weight:bold;
color:#BADA55;
margin-right:-10px;
margin-left: -10px;
padding:0.25em;
}
我们还可以发现 .widget-heading
的声明是根据其相关性排列的:.widget-heading
是行间元素,所以我们先添加字体相关的样式声明,接下来是其它的。
以下是一个没有拆分成多行的例子:
.t10 { width:10% }
.t20 { width:20% }
.t25 { width:25% } /* 1/4 */
.t30 { width:30% }
.t33 { width:33.333% } /* 1/3 */
.t40 { width:40% }
.t50 { width:50% } /* 1/2 */
.t60 { width:60% }
.t66 { width:66.666% } /* 2/3 */
.t70 { width:70% }
.t75 { width:75% } /* 3/4*/
.t80 { width:80% }
.t90 { width:90% }
在这个例子(来自inuit.css’s table grid system)中,将 CSS 放在一行内可以使得代码更紧凑。
命名规范
一般情况下我都是以连字符(-)连接 class 的名字(例如 .foo-bar
而非 .foo_bar
或 .fooBar
)。
你应当确保 class 命名得当,力保一字不多、一字不少;将元素命名抽象化以提高复用性(例如 .ui-list
,.media
)。由此延伸出去的元素命名则要尽量精准(例如 .user-avatar-link
)。不用担心 class 名的数量或长度,因为写得好的代码 gzip 也能有效压缩。
JavaScript 钩子
**千万不要把 CSS 样式用作 JavaScript 钩子。**把 JS 行为与样式混在一起将无法对其分别处理。
如果你要把 JS 和某些标记绑定起来的话,写一个 JS 专用的 class。简单地说就是划定一个前缀 .js-
的命名空间,例如 .js-toggle
,.js-drag-and-drop
。这意味着我们可以通过 class 同时绑定 JS 和 CSS 而不会因为冲突而引发麻烦。
<th class="is-sortable js-is-sortable">
</th>
上面的这个标记有两个 class,你可以用其中一个来给这个可排序的表格栏添加样式,用另一个添加排序功能。
注释
我使用行宽不超过 80 字节的块状注释:
/**
* This is a docBlock style comment
*
* This is a longer description of the comment, describing the code in more
* detail. We limit these lines to a maximum of 80 characters in length.
*
* We can have markup in the comments, and are encouraged to do so:
*
<div class=foo>
<p>Lorem</p>
</div>
*
* We do not prefix lines of code with an asterisk as to do so would inhibit
* copy and paste.
*/
/**
* 这是一个文档块(DocBlock)风格的注释。
*
* 这里开始是描述更详细、篇幅更长的注释正文。当然,我们要把行宽控制在 80 字以内。
*
* 我们可以在注释中嵌入 HTML 标记,而且这也是个不错的办法:
*
<div class=foo>
<p>Lorem</p>
</div>
*
* 如果是注释内嵌的标记的话,在它前面不加星号,否则会被复制进去。
*/
在注释中应当尽量详细描述代码,因为对你来说清晰易懂的内容对其他人可能并非如此。每写一部分代码就要专门写注释以详解。
注释的拓展用法
注释有许多很先进的用法,例如:
- 准修饰选择器
- 代码标签
- 继承标记
准修饰选择器
你应当避免过分修饰选择器,例如如果你能写 .nav{}
就尽量不要写 ul.nav{}
。过分修饰选择器将影响性能,影响 class 复用性,增加选择器私有度。这些都是你应当竭力避免的。
编写 CSS
之前的章节主要探讨如何规划 CSS,这些都是易于量化的规则。本章将探讨更理论化的东西,也将探讨我们的态度与方法。
编写新组件
编写新组件时,要在着手处理 CSS 之前写好 HTML 部分。这可以令你准确判断哪些 CSS 属性可以继承,避免重复浪费。
先写标记的话,你就可以关注数据、内容与语义,在这之后再添加需要的 class 和 CSS 样式。
布局
所有组件都不要声明宽度,而由其亲元素或格栅系统来决定。
坚决不要声明高度。高度应当仅仅用于尺寸已经固定的东西,例如图片和 CSS Sprite。在 p
,ul
,div
等元素上不应当声明高度。如果需要的话可以写 line-height
,这个更加灵活。
格栅系统应当当作书架来理解。是它们容纳内容,而不是把它们本身当成内容装起来,正如你先搭起书架再把东西放进去。比起声明它们的尺寸,把格栅系统和元素的其它属性分来开处理更有助于布局,也使得我们的前端工作更高效。
你在格栅系统上不应当添加任何样式,他们仅仅是为布局而用。在格栅系统内部再添加样式。在格栅系统中任何情况下都不要添加盒模型相关属性。
UI 尺寸
理想情况下,格栅系统应当用百分比设定。如上所述,因为我用格栅系统来固定栏宽和页宽,所以我可以不用理会元素的尺寸。
只在已经固定尺寸的元素上使用 px,包括图片以及尺寸已经用 px 固定的 CSS Sprite。
字号
定义一些与格栅系统原理类似的 class 来声明字号。这些 class 可以用于双重标题分级,关于这点请阅读 Pragmatic, practical font-sizing in CSS。
简写
CSS 简写应当谨慎使用。
编写像 background:red;
这样的属性的确很省事,但是你这么写的意思其实是同时声明 background-image:none; background-position:top left; background-repeat: repeat; background-color:red;
。虽然大多数时候这样不会出什么问题,但是哪怕只出一次问题就值得考虑要不要放弃简写了。这里应当改为 background-color:red;
。
类似的,像 margin:0;
这样的声明的确简洁清爽,但是还是应当尽量写清楚。如果你只是想修改底边边距,就要具体一些,写成 margin-bottom:0;
。
反过来,你需要声明的属性也要写清楚,不要因为简写而波及其它属性。例如如果你只想改掉底部的 margin
,那就不要用会把其它边距也清零的 margin:0
。
简写虽然是好东西,但是注意切勿滥用。
ID
在我们开始处理选择器之前,牢记这句话:
在 CSS 里尽量不要用 ID。
Class 的优势在于复用性,而且私有度也并不高。私有度非常容易导致问题,所以将其降低就尤为重要。ID 的私有度是 class 的 255 倍,所以在 CSS 中尽量不要使用。
选择器
务必保持选择器简短高效。
通过页面元素位置而定位的选择器并不理想。例如 .sidebar h3 span{}
这样的选择器就是定位过于依赖相对位置,所以把 span 移到 h3 和 sidebar 外面时就很难保持其样式。
结构复杂的选择器将会影响性能。选择器结构越复杂(如 .sidebar h3 span
为三层,.content ul p a
是四层),浏览器的消耗就越大。
尽量使得样式不依赖于其定位,尽量保持选择器简洁清晰。
作为一个整体,选择器应当尽量简短(例如只有一层结构),但是 class 名则不应当过于简略,例如 .user-avatar
就远比 .usr-avt
好。
**牢记:**class 无所谓是否语义化;应当关注它们是否合理。不要强调 class 名要符合语义,而要注重使用合理且不会过时的名称。
过度修饰的选择器
由前文所述,过度修饰的选择器并不理想。
过度修饰的选择器是指像 div.promo
这样的。很可能你只用 .promo
也能得到相同的效果。当然你可能偶尔会需要用元素类型来修饰 class(例如你写了一个 .error
而且想让它在不同的元素类型中显示效果不一样,例如 .error{ color:red; }
div.error{ padding:14px;}
),但是大多数时候还是应当尽量避免。
再举一个修饰过度的选择器例子,ul.nav li a{}
。如前文所说,我们马上就可以删掉 ul
因为我们知道 .nav
是个列表,然后我们就可以发现 a
一定在 li
中,所以我们就能将这个选择器改写成 .nav a{}
。
选择器性能
虽然浏览器性能日渐提升,渲染 CSS 速度越来越快,但是你还是应当关注效率。使用间断、没有嵌套的选择器,不把全局选择器(*{}
)用作核心选择器,避免使用日渐复杂的 CSS3 新选择器可以避免这样的问题。
译注,核心选择器:浏览器解析选择器为从右向左的顺序,最右端的元素是样式生效的元素,是为核心选择器。
使用 CSS 选择器的目的
比起努力运用选择器定位到某元素,更好的办法是直接给你想要添加样式的元素直接添加一个 class。我们以 .header ul{}
这样一个选择器为例。
假定这个 ul
就是这个网站的全站导航,它位于 header 中,而且目前为止是 header 中唯一的 ul
元素。.header ul{}
的确可以生效,但是这样并不是好方法,它很容易过时,而且非常晦涩。如果我们在 header 中再添加一个 ul
的话,它就会套用我们给这个导航部分写的样式,哪怕我们设想的不是这个效果。这意味着我们要么要重构许多代码,要么给后面的 ul
新写许多样式来抵消之前的影响。
你的选择器必须符合你要给这个元素添加样式的原因。思考一下,「我定位到这个元素,是因为它是 .header
下的 ul
,还是因为它是我的网站导航?」这将决定你应当如何使用选择器。
确保你的核心选择器不是类型选择器,也不是高级对象或抽象选择器。例如你在我们的 CSS 中肯定找不到诸如 .sidebar ul{}
或者 .footer .media{}
这样的选择器。
表达清晰:直接找到你要添加样式的元素,而非其亲元素。不要想当然地认为 HTML 不会改变。用 CSS 直接命中你需要的元素,而非投机取巧。
完整内容请参考我的文章 Shoot to kill; CSS selector intent
!important
只在起辅助作用的 class 上用 !important
。用 !important
提升优先级也可以,例如如果你要让某条规则一直生效的话,可以用 .error{ color:red!important; }
。
避免主动使用 !important
。例如 CSS 写得很复杂时不要用它来取巧,要好好整理并重构之前的部分,保持选择器简短并且避免用 ID 将效果拔群。
魔数与绝对定位
魔数(Magic Number)是指那些「凑巧有效果」的数字,这东西非常不好,因为它们只是治标不治本而且缺乏拓展性。
例如 .dropdown-nav li:hover ul{ top:37px; }
把下拉菜单移动下来远非良策,因为这里的 37px 就是个魔数。37px 会生效的原因是因为这时 .dropbox-nav
碰巧高 37px 而已。
这时你应该用 .dropdown-nav li:hover ul{ top:100%; }
,也即无论 .dropbox-down
多高,这个下拉菜单都会往下移动 100%。
每当你要在代码中放入数字的时候,请三思而行。如果你能用一个关键字(例如 top:100%
意即「从上面拉到最下面」)替换之,或者有更好的解决方法的话,就尽量避免直接出现数字。
你在 CSS 中留下的每一个数字,都是你许下而不愿遵守的承诺。
条件判断
专门为 IE 写的样式基本上都是可以避免的,唯一需要为 IE 专门处理的是为了处理 IE 不支持的内容(例如 PNG)。
简而言之,如果你重构 CSS 的话,所有的布局和盒模型都不用额外兼容 IE。也就是说你基本上不用 <!--[if IE 7]> element{ margin-left:-9px; } < ![endif]-->
或者类似的兼容 IE 的写法。
Debugging
如果你要解决 CSS 问题的话,先把旧代码拿掉再写新的。如果旧的 CSS 中有问题的话,写新代码是解决不了的。
把 CSS 代码和 HTML 部分删掉,直到没有 BUG 为止,然后你就知道问题出在哪里了。
有时候写上一个 overflow:hidden
或者其它能把问题藏起来的代码的确效果立竿见影,但是 overflow 方面可能根本就没问题。所以要治本,而不是单纯治标。
CSS 预处理器
我用 Sass。使用时应当灵活运用。用 Sass 可以令你的 CSS 更强大,但是不要嵌套得太复杂。在 Vanilla CSS 中,只在必要的地方用嵌套即可,例如:
.header{}
.header .site-nav{}
.header .site-nav li{}
.header .site-nav li a{}
这样的写法在普通 CSS 里完全用不到。以下为不好的 Sass 写法:
.header{
.site-nav{
li{
a{}
}
}
}
如果你用 Sass 的话,尽量这么写:
.header{}
.site-nav{
li{}
a{}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论