Home >Web Front-end >CSS Tutorial >CSS writing guidelines and suggestions
When participating in large-scale, long-term projects with many participants, it is extremely important for all developers to abide by the following rules:
Keep CSS easy to maintain
Keep code clear and understandable
Keep CSS scalable
In order to achieve To achieve this goal, we must adopt many methods.
The first part of this document will explore syntax, formatting, and analyzing CSS structure; the second part will focus on methodology, thinking framework, and opinions on writing and planning CSS.
CSS Document Analysis
No matter what document we write, we should maintain a unified style, including unified comments, unified grammar and unified naming conventions.
General
Control the line width below 80 bytes. Gradient-related syntax and URLs in comments can be counted as exceptions. After all, there is nothing we can do about this part.
I prefer to use 4 spaces instead of tab indentation, and split declarations into multiple lines.
Single file vs. multiple files
Some people like to write everything in one file, but I started splitting styles into multiple small files after migrating to Sass. This is all good practice. Whichever you choose, the rules below will apply, and you'll have no problems if you follow them. The difference between these two writing methods is only the directory and block title:
Directory
At the beginning of CSS, I will maintain a directory, like this:
/*------------------------------------*\ $CONTENTS \*------------------------------------*/ /** * CONTENTS............You’re reading it! * RESET...............Set our reset defaults * FONT-FACE...........Import brand font files */
This directory can tell other developers about the details in this file What content does it contain. Each item in this directory has the same block title as its corresponding block.
If you are maintaining a single file CSS, the corresponding blocks will also be in the same file. If you are writing a set of small files, then each item in the directory should have a corresponding @include statement.
Block title
The table of contents should correspond to the title of the block. As follows:
/*------------------------------------*\ $RESET \*------------------------------------*/
The block title prefix $ allows us to limit the search scope to the block title when using the [Cmd|Ctrl]+F command to find the title name.
If you are maintaining a large file, then leave 5 empty lines between blocks, like this:
/*------------------------------------*\ $RESET \*------------------------------------*/ [Our reset styles] /*------------------------------------*\ $FONT-FACE \*------------------------------------*/
These large gaps help distinguish blocks when flipping quickly through large files.
If you are maintaining multiple copies of CSS linked by include, just add a title to the header of each file, without leaving a blank line like this.
Code Order
Try to write the rules in a specific order. This will ensure that you take full advantage of the meaning of the first C in CSS: cascade.
A well-planned CSS should be arranged as follows:
Reset The root of all things
Element types without classes h1, ul, etc.
Objects and abstract content The most general and basic design pattern
Sub elements are composed of objects All extended extensions and their sub-elements
are patched for exceptions
In this way, when you write CSS in sequence, each block can automatically inherit the properties of the block before it. In this way, the parts of the code that offset each other can be reduced, some special problems can be reduced, and a more ideal CSS structure can be formed.
For more information on this, I highly recommend Jonathan Snook’s SMACSS.
CSS style set analysis
[selector]{ [property]:[value]; [<- Declaration ->] } [选择器]{ [属性]:[值]; [<- 声明 ->] }
When writing CSS styles, I am used to following these rules:
Class names are connected with hyphens (-), except for the BEM nomenclature mentioned below;
缩进 4 空格;
声明拆分成多行;
声明以相关性顺序排列,而非字母顺序;
有前缀的声明适当缩进,从而对齐其值;
缩进样式集从而反映 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 的子元素,因为前者的样式集比后者多缩进了一级。这样通过缩进就可以让开发者在阅读代码时快速获取这样的重要信息。
我们还可以发现 .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),不过在某些特定的时候我会用 BEM(Block, Element, Modifier)命名法。
BEM 命名法可以使得选择器更规范,更清晰,更具语义。
该命名法按照如下格式:
.block{} .block__element{} .block--modifier{}
其中:
.block 代表某个基本的抽象元素;
.block__element 代表构成 .block 的一个子元素;
.block--modifier 代表 .block 的某个不同状态或版本。
打个比方:
.person{} .person--woman{} .person__hand{} .person__hand--left{} .person__hand--right{}
这个例子中我们描述的基本元素是一个人,然后这个人可能是一个女人。我们还知道人拥有手,这些是人体的一部分,而手也有不同的状态,如同左手与右手。
这样我们就可以根据亲元素来划定选择器的命名空间并传达该选择器的职能,例如根据这个选择器是一个子元素(__)还是其亲元素的不同状态(--)。
由此,.page-wrapper 是一个独立的选择器。这是一个符合规范的命名,因为它不是其它元素的子元素或其它状态;然而 .widget-heading 则与其它对象有关联,它应当是 .widget 的子元素,所以我们应当将其重命名为 .widget__heading。
BEM 命名法虽然不太好看,而且相当冗长,但是它使得我们可以通过名称快速获知元素的功能和元素之间的关系。与此同时,BEM 语法中的重复部分非常有利于 gzip 的压缩算法。
无论你是否使用 BEM 命名法,你都应当确保 class 命名得当,力保一字不多、一字不少;将元素命名抽象化以提高复用性(例如 .ui-list,.media)。子元素的命名则要尽量精准(例如 .user-avatar-link)。不用担心 class 名的数量或长度,因为写得好的代码 gzip 也能有效压缩。
HTML 中的 class
为了确保易读性,在 HTML 标记中用两个空格隔开 class 名,例如:
<div class="foo--bar bar__baz">
增加的空格应当可以使得在使用多个 class 时更易阅读与定位。
JavaScript 钩子
切勿将标记 CSS 样式的 class 用作 JavaScript 钩子。把 JS 行为与样式混在一起将无法对其分别处理。
如果你要把 JS 和某些标记绑定起来的话,写一个 JS 专用的 class。简单地说就是划定一个前缀 .js- 的命名空间,例如 .js-toggle,.js-drag-and-drop。这意味着我们可以通过 class 同时绑定 JS 和 CSS 而不会因为冲突而引发麻烦。
<th class="is-sortable js-is-sortable"> </th>
上面的这个标记有两个 class,你可以用其中一个来给这个可排序的表格栏添加样式,用另一个添加排序功能。
I18n
虽然我(该 CSS Guideline 文档原作者 Harry Roberts)是个英国人,而且我一向拼写 colour 而非 color,但是为了追求统一,我认为在 CSS 中用美式拼法更佳。CSS 以及其它多数语言都是以美式拼法编写,所以如果在 .colour-picker{} 中写 color:red 就缺乏统一性。我以前主张同时用两种拼法,例如:
.color-picker, .colour-picker{ }
但是我最近参与了一份规模庞大的 Sass 项目,这个项目中有许多的颜色变量(例如 $brand-color,$highlight-color 等等),每个变量要维护两种拼法实在辛苦,要查找并替换时也需要两倍的工作量。
所以为了统一,把所有的 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> * * 如果是注释内嵌的标记的话,在它前面不加星号,以免被复制进去。 */
在注释中应当尽量详细描述代码,因为对你来说清晰易懂的内容对其他人可能并非如此。每写一部分代码就要专门写注释以详解。
注释的拓展用法
注释有许多很高级的用法,例如:
准修饰选择器(Quasi-qualified selectors)
代码标签
继承标记
准修饰选择器(Quasi-qualified selectors)
你应当避免过分修饰选择器,例如如果你能写 .nav{} 就尽量不要写 ul.nav{}。过分修饰选择器将影响性能,影响 class 复用性,增加选择器私有度。这些都是你应当竭力避免的。
不过有时你可能希望告诉其他开发者 class 的使用范围。以 .product-page 为例,这个 class 看起来像是一个根容器,可能是 html 或者 body 元素,但是仅凭 .product-page 则无法判断。
我们可以在选择器前加上准修饰(即将前面的类型选择器注释掉)来描述我们规划的 class 作用范围:
/*html*/.product-page{}
这样我们就能准确获知该 class 的作用范围而不会影响复用性。
其它例子如:
/*ol*/.breadcrumb{} /*p*/.intro{} /*ul*/.image-thumbs{}
这样我们就能在不影响代码私有度的前提下获知 class 作用范围。
代码标签
如果你写了一组新样式的话,可以在它上面加上标签,例如:
/** * ^navigation ^lists */ .nav{} /** * ^grids ^lists ^tables */ .matrix{}
这些标签可以使得其他开发者快速找到相关代码。如果一个开发者需要查找和列表相关的部分,他只要搜索 ^lists 就能快速定位到 .nav,.matrix 以及其它相关部分。
继承标记
将面向对象的思路用于 CSS 编写的话,你经常能找到两部分 CSS 密切相关(其一为基础,其一为拓展)却分列两处。我们可以用继承标记来在原元素和继承元素之间建立紧密联系。这些在注释中的写法如下:
在元素的基本样式中:
/** * Extend `.foo` in theme.css */ .foo{} 在元素的拓展样式中: /** * Extends `.foo` in base.css */ .bar{}
这样一来我们就能在两块相隔很远的代码间建立紧密联系。
编写 CSS
之前的章节主要探讨如何规划 CSS,这些都是易于量化的规则。本章将探讨更理论化的东西,也将探讨我们的态度与方法。
编写新组件
编写新组件时,要在着手处理 CSS 之前写好 HTML 部分。这可以令你准确判断哪些 CSS 属性可以继承,避免重复浪费。
先写标记的话,你就可以关注数据、内容与语义,在这之后再添加需要的 class 和 CSS 样式。
面向对象 CSS
我以面向对象 CSS 的方式写代码。我把组件分成结构(对象)与外观(拓展)。正如以下分析(注意此处并非示例):
.room{} .room--kitchen{} .room--bedroom{} .room--bathroom{}
我们在屋子里有许多房间,它们都有共同的部分:地板、天花板、墙壁和门。这些共享的部分我们可以放到一个抽象的 .room{} class 中。不过我们还有其它与众不同的房间:一个厨房可能有地砖,卧室可能有地毯,洗手间可能没有窗户但是卧室会有,每个房间的墙壁颜色也许也会不一样。面向对 象 CSS 的思路使得我们把相同部分抽象出来组成结构部分,然后用更具体的 class 来拓展这些特征并添加特殊的处理方法。
所以比起编写大量各自不同的模块,应当努力找出这些模块中重复的设计模式并将其抽象出来,写成一个可以复用的 class,将其用作基础然后编写其它拓展模块的特殊情形。
当你要编写一个新组件时,将其拆分成结构和外观。编写结构部分时用最通用 class 以保证复用性,编写外观时用更具体的 class 来添加设计方法。
布局
所有组件都不要声明宽度,而由其亲元素或格栅系统来决定。
坚决不要声明高度。高度应当仅仅用于尺寸已经固定的东西,例如图片和 CSS Sprite。在 p,ul,div 等元素上不应当声明高度。如果需要的话可以使用更加灵活的 line-height。
格栅系统应当当作书架来理解。是它们容纳内容,而不是把它们本身当成内容装起来,正如你先搭起书架再把东西放进去。比起声明它们的尺寸,把格栅系统和元素的其它属性分来开处理更有助于布局,也使得我们的前端工作更高效。
你在格栅系统上不应当添加任何样式,它们仅仅是为布局而用。在格栅系统内部再添加样式。在格栅系统中任何情况下都不要添加盒模型相关属性。
UI 尺寸
我用很多方法设定 UI 尺寸,包括百分比,px,em,rem 以及干脆什么都不用。
理想情况下,格栅系统应当用百分比设定。如上所述,因为我用格栅系统来固定栏宽和页宽,所以我可以不用理会元素的尺寸。
我用 rem 定义字号,并且辅以 px 以兼容旧浏览器。这可以兼具 em 和 px 的优势。下面是一个非常漂亮的 Sass Mixin,假设你在别处声明了基本字号(base-font-size)的话,用它就可以生成 rem 以及兼容旧浏览器的 px。
@mixin font-size($font-size){ font-size:$font-size +px; font-size:$font-size / $base-font-size +rem; }
我只在已经固定尺寸的元素上使用 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。
在 HTML 里 ID 可以用于 JS 以及锚点定位,但是在 CSS 里只要用 class,一个 ID 也不要用。
Class 的优势在于复用性,而且私有度也并不高。在项目中私有度非常容易导致问题,所以将其降低就尤为重要。ID 的私有度是 class 的 255 倍,所以在 CSS 中坚决不要使用。
选择器
务必保持选择器简短高效。
Selectors positioned by the position of page elements are not ideal. For example, the positioning of selectors such as .sidebar h3 span{} relies too much on relative position. If span is moved outside h3 and sidebar, it will be difficult to maintain its style.
Selectors with complex structures will affect performance. The more complex the selector structure is (for example, .sidebar h3 span is three layers, .content ul p a is four layers), the greater the browser overhead.
Try to make the style independent of its positioning, and try to keep the selector simple and clear.
As a whole, the selector should be as short as possible (for example, only one layer of structure), but the class name should not be too simple. For example, .user-avatar is far better than .usr-avt.
Keep in mind: it doesn’t matter whether classes are semantic or not; you should focus on whether they are reasonable. Don’t stress about class names being semantic, but focus on using names that are sensible and not outdated.
Over-modified selectors
As mentioned above, over-modified selectors are not ideal.
Over-modified selectors are those like div.promo. Most likely you can get the same effect using just .promo. Of course, you may occasionally need to modify the class with the element type (for example, if you write an .error and want it to display differently in different element types, for example.error { color: red; } div.error { padding: 14px; }), but should be avoided most of the time.
Another example of an over-modified selector is ul.nav li a{}. As mentioned before, we can delete ul immediately because we know that .nav is a list, and then we can find that a must be in li, so we can rewrite this selector into .nav a{}.
Selector performance
Although browser performance is improving day by day and rendering CSS is getting faster and faster, you should still pay attention to efficiency. This problem can be avoided by using short, non-nested selectors, not using global selectors (*{}) as core selectors, and avoiding the increasingly complex CSS3 new selectors.
Translation, core selector: The browser parses selectors in order from right to left. The rightmost element is the element whose style takes effect, and is the core selector.
The purpose of using CSS selectors
Rather than trying to use selectors to locate an element, a better way is to directly add a class to the element you want to style. Let's take a selector like .header ul {} as an example.
Assume that this ul is the site-wide navigation of this website, it is located in the header, and it is the only ul element in the header so far. .header ul{} does work, but it's not a good idea, it's easily outdated, and it's very obscure. If we add another ul to the header, it will apply the style we wrote for this navigation part, even if this is not the effect we envisioned. This means that we either have to refactor a lot of code, or write a lot of new styles for the later ul to offset the previous impact.
Your selector must fit the reason why you want to style this element. Think about it, "Am I targeting this element because it's a ul under .header, or because it's my site navigation?" This will determine how you should use the selector.
Make sure your core selector is not a type selector, nor a high-level object or abstract selector. For example, you will definitely not find selectors such as .sidebar ul {} or .footer .media {} in our CSS.
Clear expression: directly find the element you want to add style to, not its parent elements. Don't assume that HTML won't change. Use CSS to hit the elements you need directly, rather than opportunistically.
For the complete content, please refer to my article Shoot to kill; CSS selector intent
!important
Only use !important on the auxiliary class. You can also use !important to increase the priority. For example, if you want a certain rule to be effective all the time, you can use .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。也就是说你基本上不用 ebfe8dd00e7ea47e29f0d93ec8c68f66 element{ margin-left:-9px; } bd121e40e132cd2f69209d509c354edd 或者类似的兼容 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{} }