Bei der Arbeit an großen, langwierigen Projekten mit vielen Beteiligten ist es äußerst wichtig, dass alle Entwickler die folgenden Regeln einhalten:
CSS leicht wartbar halten
Code klar und verständlich halten
Skalierbarkeit von CSS beibehalten
Um dieses Ziel zu erreichen, müssen wir viele Methoden verwenden.
Der erste Teil dieses Dokuments befasst sich mit Syntax, Formatierung und Analyse der CSS-Struktur. Der zweite Teil konzentriert sich auf die Methodik, den Denkrahmen und Meinungen zum Schreiben und Planen von CSS.
CSS-Dokumentenanalyse
Unabhängig davon, welches Dokument wir schreiben, sollten wir einen einheitlichen Stil beibehalten, einschließlich einheitlicher Kommentare, einheitlicher Grammatik und einheitlicher Namenskonventionen.
Allgemeines
Beschränken Sie die Zeilenbreite auf weniger als 80 Byte. Farbverlaufsbezogene Syntax und URLs in Kommentaren können als Ausnahmen gezählt werden. Schließlich können wir an diesem Teil nichts ändern.
Ich bevorzuge die Verwendung von 4 Leerzeichen anstelle des Tabulatoreinzugs und die Aufteilung von Deklarationen in mehrere Zeilen.
Einzelne Datei vs. mehrere Dateien
Manche Leute schreiben gerne alles in eine Datei, aber nach der Migration zu Sass habe ich angefangen, Stile in mehrere kleine Dateien aufzuteilen. Das ist alles eine gute Übung. Wofür Sie sich auch entscheiden, es gelten die folgenden Regeln, und wenn Sie diese befolgen, werden Sie keine Probleme haben. Der Unterschied zwischen diesen beiden Schreibmethoden besteht nur im Verzeichnis und Blocktitel:
Verzeichnis
Zu Beginn von CSS werde ich ein Verzeichnis wie dieses pflegen:
/*------------------------------------*\ $CONTENTS \*------------------------------------*/ /** * CONTENTS............You’re reading it! * RESET...............Set our reset defaults * FONT-FACE...........Import brand font files */
Dieses Verzeichnis kann anderen Entwicklern mitteilen, welcher Inhalt in dieser Datei enthalten ist. Jedes Element in diesem Verzeichnis hat denselben Blocktitel wie der entsprechende Block.
Wenn Sie ein einzelnes Datei-CSS verwalten, befinden sich die entsprechenden Blöcke auch in derselben Datei. Wenn Sie eine Reihe kleiner Dateien schreiben, sollte jedes Element im Verzeichnis eine entsprechende @include-Anweisung haben.
Blocktitel
Das Inhaltsverzeichnis sollte dem Titel des Blocks entsprechen. Wie folgt:
/*------------------------------------*\ $RESET \*------------------------------------*/
Das Blocktitel-Präfix $ ermöglicht es uns, den Suchbereich auf den Blocktitel zu beschränken, wenn wir den F-Befehl [Cmd|Strg] verwenden, um den Titelnamen zu finden.
Wenn Sie eine große Datei verwalten, lassen Sie wie folgt 5 Zeilen zwischen den Blöcken leer:
/*------------------------------------*\ $RESET \*------------------------------------*/ [Our reset styles] /*------------------------------------*\ $FONT-FACE \*------------------------------------*/
Wenn Sie schnell durch eine große Datei blättern, helfen diese großen Lücken bei der Unterscheidung von Blöcken.
Wenn Sie mehrere Kopien von CSS verwalten, die durch Include verbunden sind, fügen Sie einfach einen Titel zum Header jeder Datei hinzu, ohne eine Leerzeile wie diese hinterlassen zu müssen.
Codereihenfolge
Versuchen Sie, die Regeln in einer bestimmten Reihenfolge zu schreiben. Dadurch wird sichergestellt, dass Sie die Bedeutung des ersten C in CSS: cascade voll ausnutzen.
Ein gut geplantes CSS sollte wie folgt aufgebaut sein:
Die Wurzel aller Dinge zurücksetzen
Elementtyp h1, ul usw. ohne Klasse
Objekt und Das allgemeinste und grundlegendste Entwurfsmuster für abstrakte Inhalte
Alle Erweiterungen von Unterelementen, die von Objekten und ihren Unterelementen erweitert werden
Patch für abnormale Zustände
In Auf diese Weise kann beim sequentiellen Schreiben von CSS jeder Block automatisch die Eigenschaften des Blocks davor erben. Auf diese Weise können die Teile des Codes, die sich gegenseitig aufheben, reduziert, einige spezielle Probleme reduziert und eine idealere CSS-Struktur gebildet werden.
Für weitere Informationen hierzu empfehle ich Jonathan Snooks SMACSS.
Analyse von CSS-Stilsätzen
[selector]{ [property]:[value]; [<- Declaration ->] } [选择器]{ [属性]:[值]; [<- 声明 ->] }
Beim Schreiben von CSS-Stilen bin ich es gewohnt, diese Regeln zu befolgen:
Klassennamen werden mit Bindestrichen (-) verbunden, außer als unten erwähnte BEM-Nomenklatur;
缩进 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 中坚决不要使用。
选择器
务必保持选择器简短高效。
Selektoren, die nach der Position des Seitenelements positioniert sind, sind nicht ideal. Beispielsweise hängt die Positionierung eines Selektors wie .sidebar h3 span{} zu sehr von der relativen Position ab. Wenn der Bereich außerhalb von h3 und der Seitenleiste verschoben wird, ist es schwierig, seinen Stil beizubehalten.
Ein Selektor mit einer komplexen Struktur beeinträchtigt die Leistung. Je komplexer die Selektorstruktur ist (z. B. .sidebar h3 span besteht aus drei Ebenen, .content ul p a besteht aus vier Ebenen), desto größer ist der Browser-Overhead.
Versuchen Sie, den Stil unabhängig von seiner Positionierung zu gestalten und den Selektor einfach und klar zu halten.
Insgesamt sollte der Selektor so kurz wie möglich sein (z. B. nur eine Strukturebene), aber der Klassenname sollte nicht zu einfach sein. Beispielsweise ist .user-avatar weitaus besser als .usr-avt.
Denken Sie daran: Es spielt keine Rolle, ob Klassen semantisch sind oder nicht. Sie sollten sich darauf konzentrieren, ob sie sinnvoll sind. Machen Sie sich keine Gedanken darüber, dass Klassennamen semantisch sind, sondern konzentrieren Sie sich darauf, Namen zu verwenden, die sinnvoll und nicht veraltet sind.
Übermodifizierte Selektoren
Wie oben erwähnt, sind übermodifizierte Selektoren nicht ideal.
Übermodifizierte Selektoren sind solche wie div.promo. Höchstwahrscheinlich können Sie den gleichen Effekt nur mit .promo erzielen. Natürlich müssen Sie möglicherweise gelegentlich die Klasse mit dem Elementtyp ändern (z. B. schreiben Sie eine .error-Datei und möchten, dass sie in verschiedenen Elementtypen unterschiedlich angezeigt wird, z. B..error { color: red; } div.error { padding : 14px; }), sollte aber meistens vermieden werden.
Geben Sie ein weiteres Beispiel für einen übermodifizierten Selektor: ul.nav li a{}. Wie bereits erwähnt, können wir ul sofort löschen, da wir wissen, dass .nav eine Liste ist, und dann können wir feststellen, dass a in li sein muss, damit wir diesen Selektor in .nav a{} umschreiben können.
Selektorleistung
Obwohl sich die Browserleistung von Tag zu Tag verbessert und das Rendern von CSS immer schneller wird, sollten Sie dennoch auf Effizienz achten. Dieses Problem kann vermieden werden, indem kurze, nicht verschachtelte Selektoren verwendet werden, keine globalen Selektoren (*{}) als Kernselektoren verwendet werden und die immer komplexer werdenden neuen CSS3-Selektoren vermieden werden.
Übersetzung, Kernselektor: Der Browser analysiert Selektoren in der Reihenfolge von rechts nach links. Das Element ganz rechts ist das Element, für das der Stil wirksam wird, und ist der Kernselektor.
Der Zweck der Verwendung von CSS-Selektoren
Anstatt zu versuchen, Selektoren zum Auffinden eines Elements zu verwenden, besteht eine bessere Möglichkeit darin, dem Element, dem Sie Stile hinzufügen möchten, direkt eine Klasse hinzuzufügen. Nehmen wir als Beispiel einen Selektor wie .header ul {}.
Gehen Sie davon aus, dass es sich bei diesem UL um die Site-weite Navigation dieser Website handelt. Es befindet sich im Header und ist bisher das einzige UL-Element im Header. .header ul{} funktioniert zwar, aber es ist keine gute Idee, es ist leicht veraltet und sehr undurchsichtig. Wenn wir im Header ein weiteres ul hinzufügen, wird der Stil angewendet, den wir für diesen Navigationsteil geschrieben haben, auch wenn dies nicht der Effekt ist, den wir uns vorgestellt haben. Das bedeutet, dass wir entweder viel Code umgestalten oder viele neue Stile für die spätere UL schreiben müssen, um die vorherigen Auswirkungen auszugleichen.
Ihr Selektor muss mit dem Grund übereinstimmen, warum Sie dieses Element formatieren möchten. Denken Sie darüber nach: „Ziele ich auf dieses Element, weil es ein UL unter .header ist, oder weil es die Navigation meiner Website ist?“ Dies bestimmt, wie Sie den Selektor verwenden sollten.
Stellen Sie sicher, dass Ihr Kernselektor weder ein Typselektor noch ein übergeordneter Objekt- oder abstrakter Selektor ist. Beispielsweise werden Sie in unserem CSS definitiv keine Selektoren wie .sidebar ul {} oder .footer .media {} finden.
Klarer Ausdruck: Suchen Sie direkt das Element, dem Sie Stile hinzufügen möchten, nicht seine übergeordneten Elemente. Gehen Sie nicht davon aus, dass sich HTML nicht ändern wird. Verwenden Sie CSS, um die benötigten Elemente direkt und nicht opportunistisch anzusprechen.
Für den vollständigen Inhalt lesen Sie bitte meinen Artikel Shoot to kill; CSS Selector Intent
!important
Verwenden Sie !important nur für Hilfsklassen. Sie können auch !important verwenden, um die Priorität zu erhöhen. Wenn Sie beispielsweise möchten, dass eine bestimmte Regel immer wirksam ist, können Sie .error { color:red!important } verwenden.
避免主动使用 !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。也就是说你基本上不用 或者类似的兼容 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{} }