在 Mozilla UI 中书写高效率 CSS
注:本文非原创,点击查看原帖
以下文档描述了应用在 Mozilla UI 中优化 CSS 文件的规则。第一部分是对于 Mozilla 样式系统分类规则的一般性讨论。在了解这个系统的基础上,后续部分包含了一些指南,书写可以利用这个样式系统实践优点的样式的指南。
样式系统如何分类规则
样式系统把规则分为四大类。理解这些类是很重要的,因为对于规则的匹配来说他们是首先要考虑的。之后的段落中会使用“主选择符”这个说法。主选择符是指选择符最右边的部分(最终要匹配的那个元素,而不是它的祖先元素)。例如,在以下规则中:
a img, div > p, h1 + [title] {}
主选择符是 “img”, “p”, 和 “[title]“。
ID 规则
ID 选择符作为主选择符的规则。
例如:
- button#backButton { } /* ID 类别的规则 */
- #urlBar[type="autocomplete"] { } /* ID 类别的规则 */
- treeitem > treerow > treecell#myCell:active { } /* ID 类别的规则 */
Class 规则
如果一条规则有一个指定的 class 作为主选择符,就被归入此类。
例如:
- button.toolbarButton { } /* 基于 class 的规则 */
- .fancyText { } /* 基于 class 的规则 */
- menuitem > .menu-left[checked="true"] { } /* 基于 class 的规则 */
Tag 规则
如果主选择符不是 ID 或者 class,那么下一个类很可能就是 tag 分类。如果一条规则指定 tag 为主选择符,就被归入此类。
例如:
- td { } /* 基于 tag 的规则 */
- treeitem > treerow { } /* 基于 tag 的规则 */
- input[type="checkbox"] { } /* 基于 tag 的规则 */
全局规则
除以上分类之外都归入此类。
例如:
- [hidden="true"] { } /* 全局规则 */
- *{} /* 全局规则 */
- tree > [collapsed="true"] { } /* 全局规则 */
样式系统如何匹配规则
样式系统从最右边的选择符开始向左侧移动来匹配一条规则。样式系统会一直向左匹配选择符直到规则匹配完毕或者由于出错停止匹配。
对于规则分类的第一步发生在主选择符类别基础上。这个分类的目的是把那些不需要浪费时间匹配的规则过滤出来。这是显著提升性能的重点。对于一个给定的需要匹配的元素,规则越少,样式的渲染越快。例如,元素有一个 ID,那么只有和元素 ID 匹配的 ID 规则会被检索。只有和元素的 class 匹配的 class 规则会被检索。只有和 tag 匹配的 tag 规则会被检索。全局规则总是会被检索。
高效 CSS 指南
避免全局规则
确保规则不以全局分类结束
不要给 ID 分类规则指定标签名或者 class
如果有一条样式规则是以 ID 选择符为主选择符的,就别再画蛇添足的加上标签名了。ID 都是唯一的,因此加上标签名反而会无谓地拖慢匹配过程。(当有不同元素使用同一类名,而又需要动态地改变其中一个元素的类名来针对不同情况应用不同样式时是个例外。)
- BAD – button#backButton { }
- BAD – .menu-left#newMenuIcon { }
- GOOD – #backButton { }
- GOOD – #newMenuIcon { }
不要给 class 分类规则指定标签名
和以上规则类似,所有的类名也是唯一的。标签和类名连缀的写法是一个惯例(但是,如果设计的变更使得需要改变标签就会有灵活性的问题,因为也需要改变 class — 最好选用具有严格语义的名字,这种灵活性也是分离样式表的目标之一)。
- BAD – treecell.indented { }
- GOOD – .treecell-indented { }
- BEST – .hierarchy-deep { }
尽量把规则应用到最明确的类上。
拖慢系统最大的一个原因是有太多的 tag 分类规则了。通过给元素增加类名,可以把这些 tag 类里的规则分一部分去class 分类,就可以不需要浪费时间来试图给一个标签匹配那么多的的规则了。
- BAD – treeitem[mailfolder="true"] > treerow > treecell { }
- GOOD – .treecell-mailfolder { }
避免后代选择符
CSS 中,后代选择符(descendant)[注1]的耗能高的可怕,尤其是选择符的规则是在 tag 或者全局分类中。子选择符(child selector)则经常是真正所需。如果没有主题模块所有者的明确允许,UI CSS 中禁止使用后代选择符。
- BAD – treehead treerow treecell { }
- BETTER, BUT STILL BAD (see next guideline) – treehead > treerow > treecell { }
Tag 类规则中最好不要包含后代选择符
避免使用具有 tag 类规则的后代选择符。这会明显地增加匹配时间(尤其是这些规则会被多次匹配时)。
- BAD – treehead > treerow > treecell { }
- BEST – .treecell-header { }
小心子选择符的使用
要小心使用子代选择符。如果有别的方式可以不用,就不要用子选择符。尤其是子选择符被大量使用在 RDF 树和类似的菜单中时。
- BAD – treeitem[IsImapServer="true"] > treerow > .tree-folderpane-icon { }
要注意模版中来自 RDF 的属性是可以复制的!可以利用这一点把 RDF 属性复制到需要改变那个属性的子元素上。
- GOOD – .tree-folderpane-icon[IsImapServer="true"] { }
倚赖继承
了解并使用那些可以继承的属性。XUL widgetry[注2] 已经明确设置了,因此可以把 list-style-image 或者 font 规则应用到父级标签上,它将渗透进匿名内容。因此就不需要浪费时间为那些匿名内容写规则了。
- BAD – #bookmarkMenuItem > .menu-left { list-style-image: url(blah); }
- GOOD – #bookmarkMenuItem { list-style-image: url(blah); }
上例中,样式化匿名内容的需求(没有理解 list-style-image 可以继承)导致了一条 class 类规则。其实这条规则应该属于最明确的一类 — ID 类规则。
要记住,尤其是那些匿名内容,它们都有同样的 class。上面那个不好的例子导致需要检查每个菜单的图标是否包含在 bookmarks 菜单项中。这是非常可怕的高昂消耗(有很多菜单);除 bookmarks 菜单之外,这条规则不应该被其他任何菜单想检查。
使用 -moz-image-region
把一堆图片放到一个单独的图片文件中,并用 -moz-image-region[注3] 选中会有显著的性能提升。()
注1: 后代选择符(descendant selector) 子选择符(child selector) 是有区别的。单从字面来讲,后代选择符,顾名思义包括儿子、孙子、重孙子等所有后代; 子选择符只是指儿子,就不管那帮孙子了。
注2: XUL widgetry 不清楚是啥。
注3: 貌似想法和现在说的 CSS sprites 方法差不多,要知道这可是十年前(2000年)的文章啊。 – 糖伴西红柿