CSS选择器的命名是一个哲学问题

2021年8月5日19:31:48
评论
168

如果你正在参与的是一个独自开发、页面简单且上线几天就寿终正寝的小项目,则你可以完全放飞自我,CSS选择器可以随便命名,中文、emoji字符、各种高级选择器都可以用起来。但是,如果你正在开发多人协作,需要不断迭代、不断维护的项目,则一定要谨慎设计,考虑周全,以职业的态度面对命名这件事情。

CSS选择器的命名是一个哲学问题

自然,开发人员并不傻,也知道对于有些项目,要尽心尽力,他们会发挥出自己的巅峰实力,项目上线后也自我感觉良好。但那些自我感觉良好的开发人员写的CSS代码实际上往往质量堪忧,但开发人员却压根没意识到这个问题,最典型的就是CSS命名的设计很糟糕,他们早已经埋下巨大的隐患却浑然不知。

这样的现象太多了,真的太多了。正因为如此,我觉得有必要好好和大家聊聊CSS选择器命名的问题,先把选择器的CSS代码质量给提升上去。

一、长命名还是短命名

对于使用长命名还是短命名的问题,我的回答是请使用短命名。例如,一段介绍,类名可以这样:

.some-intro { line-height: 1.75; }

而没有必要这样:

.some-introduction { line-height: 1.75; }

后一种方式不仅增加了书写时间,也增加了CSS文件的大小。虽然这样做使语义更加准确了,也确实有一定价值,但价值很有限。要知道,日后维护代码时,人们只会关心这个类名有没有在其他地方使用过?改变、删除这个类名会不会出现问题?至于语义,人们真的不关心。

CSS选择器的语义和HTML的语义是不一样的,前者只是为了方便人的识别,它对于机器而言没有任何区别,因此价值很弱;但是HTML的语义的重要作用是让机器识别,如搜索引擎或者屏幕阅读器等,它是与用户体验与产品价值密切相关的。

因此,请使用短命名,足矣!一旦习惯,或者约定俗成,完全不影响阅读,就好比<p>标签是paragraph的简写,语义表示段落一样。

二、单命名还是组合命名

单命名的优点是字符少、书写快,缺点是容易出现命名冲突的问题;组合命名的优点是不容易出现命名冲突,但写起来较烦琐。样式冲突的性质比书写速度慢严重得多,因此,理论上推荐使用组合命名,但在实际开发中,项目追求的往往是效益最大化,而不是完美的艺术品。因此,具体该如何取舍,不能一概而论,只能从经验层面进行阐述。

(1)对于多人合作、长期维护的项目,千万不要出现下面这些以常见单词命名的单命名选择器,因为后期非常容易出现命名冲突的问题,即使你的项目不会引入第三方的CSS:

.title {} /* 不建议 */
.text {} /* 不建议 */
.box {} /* 不建议 */

这几个命名是出现频率最高的,一定要使用另外的前缀组合将它们保护起来,这个前缀可以是模块名称,或者场景名称,例如:

.dialog-title {}
.ajax-error-text {}
.upload-box {}

(2)如果你的项目会使用第三方的UI组件,就算是全站公用的CSS,也不要出现下面这样的单命名,因为说不定下面的命名就会与第三方CSS发生冲突:

.header {} /* 不建议 */
.main {} /* 不建议 */
.aside {} /* 不建议 */

.warning {} /* 不建议 */
.success {} /* 不建议 */

.red {} /* 不建议 */
.green {} /* 不建议 */

正确的做法是加一个统一的前缀,使用组合命名的方式。你可以随意命名这个前缀,可以是项目代号的英文缩写,也可以是产品名称的拼音首字母,因为这个前缀的作用是避免冲突,它并不需要任何语义。但需要注意的是前缀最好不要超过4个字母,因为字母多了完全没有任何意义,只会徒增CSS文件的大小。例如,“CSS选择器”的英文是CSS Selector,我就可以取CSS的首字母C和Selector的首字母S作为本书所有选择器的前缀类名,于是有:

.cs-header {}
.cs-main {}
.cs-aside {}
...

如果你认真观察所有的开源UI框架,会发现其CSS样式一定都有一个一致的前缀,因为这样做会避免发生冲突,我们自己开发项目的时候也要秉承这个理念。
(3)如果你的项目百分百是自主研发的,以后维护此项目的人也不会盗取别人的CSS来充数,则与网站公用结构、颜色相关的这些CSS可以使用单命名,例如:

.dark { color: #4c5161; }
.red { color: #f4615c; }
.gray { color: #a2a9b6; }

但对于非公用内容,如标题(.title)、盒子(.box)等就不能使用单命名,因为颜色这类样式是贯穿于整个项目的,具有高度的一致性,而标题(.title)会在很多地方出现,且样式各不相同,如大标题、小标题、弹框标题、模块标题等,容易产生命名冲突。

对于网站UI组件,各个业务模块一定要采用多名称的组合命名方式,且最好都有一个统一的命名前缀。

(4)如果你做的项目并不需要长期维护,也不需要多人合作,例如,只是一些运营活动,请务必添加统一的项目前缀,这都是过来人的忠告,因为这次活动的某些功能和效果日后会被复用,有了统一的前缀,日后直接复制代码就能使用,没有后顾之忧,大家都开心,例如:

.cs-title {}
.cs-text {}
.cs-box {}

但有一类基于CSS属性构建的单命名反而更安全,它们比颜色这些类名还要安全,即使项目会引入外部CSS:

.db { display: block; }
.tc { text-align: center; }
.ml20 { margin-left: 20px; }
.vt { vertical-align: top; }

这种方式的命名更安全的原因在哪里呢?

(1)这些选择器命名是面向CSS属性的,它们是超越具体项目的存在,只会被重复定义,但不会发生样式冲突。

(2)面向CSS属性的命名是机械的、反直觉的,而面向语义的命名符合人类直觉,也就是说,对于一个标题,将它命名为title的人很多,但抛弃语义,直接使用tc命名的人却寥寥无几。更直白一点,从网上随机找两个CSS文件,其中title命名冲突的概率要比tc大好几个数量级。

这确实有些奇怪,如此短的命名反而不会产生冲突,这是我这10年来写过无数CSS所得出的结论。当然,我们最好还是尽可能降低冲突出现的概率,这样心里也踏实:

.g-db { display: block; }
.g-tc { text-align: center; }
.g-ml20 { margin-left: 20px; }
.g-vt { vertical-align: top; }

或者连前缀也直接省掉:

.-db { display: block; }
.-tc { text-align: center; }
.ml20 { margin-left: 20px; }
.-vt { vertical-align: top; }

这样,一眼就能辨识这个类名是基于CSS属性创建的。

总结一下,除了多人合作、长期维护、不会引入第三方CSS的项目的全站公用样式可以使用单命名,其他场景都需要组合命名。

然而,即使将命名做到极致,也无法完全避免冲突,因为CSS reset的冲突是防不胜防的。例如,对于body标签选择器的设置,每个网站都不一样,很多第三方CSS甚至喜欢使用通配符:

*, *::before, *::after { box-sizing: border-box; }

后面2个伪元素前面的星号是多余的,这不重要,重要的是这段CSS会给其他网站布局带来毁灭性的影响,导致大量错位和尺寸变化,因为所有元素默认的盒模型都被改变了。希望大家在实际开发中不会遇到这样不靠谱的第三方,也不要成为这么不靠谱的第三方。

三、面向属性的命名和面向语义的命名

面向属性的命名指选择器的命名是跟着具体的CSS样式走的,与项目、页面、模块统统没有关系。例如,比较经典的清除浮动类名

.clearfix:.clearfix:after { content: ''; display: table; clear: both; }

以及其他很多命名:

.dn { display: none; }
.db { display: block; }
.df { display: flex; }
.dg { display: grid; }
.fl { float: left; }
.fr { float: right; }
.tl { text-align: left; }
.tr { text-align: right; }
.tc { text-align: center; }
.tj { text-align: justify; }
...

面向语义的命名则是根据应用元素所处的上下文来命名的。例如:

.header { background-color: #333; color: #fff; }
.logo { font-size: 0; color: transparent; }
...

上述两种命名方式各有优缺点。

面向属性的命名的优点在于CSS的重用率高,性能最佳,即插即用,方便快捷,开发也极为迅速,因为它省去了大量在HTML和CSS文件之间切换的时间;不足在于由于属性单一,其适用场景有限,另外因为使用方便,易被过度使用,从而带来更高的维护成本。

面向语义的命名的优点是应用场景广泛,可以实现非常精致的布局效果,扩展方便;不足在于代码啰唆,开发效率一般,因为所有HTML都需要命名,哪怕是一个10像素的间距。这就导致很多开发者要么选择直接使用标签选择器,要么就选择一个简单的类名,然后通过父子关系限定样式,结果带来了更糟糕的维护问题。

.cs-foo > div { margin-top: 10px; }
.cs-foo .bar { text-align: center; }

两种选择器命名的优缺点对比见下表。

CSS选择器的命名是一个哲学问题

针对这两种命名,究竟该如何取舍?我的观点是:如果是小项目,则直接采用面向语义的命名方式;如果是多人合作的大项目,则两种方式都采用,因为项目越大,面向属性的命名的价值越能得到体现。这一点会在下一节深入探讨。

四、我是如何取名的

给选择器命名就和中午吃什么一样是一个难题。命名不能太长(如果类名可以压缩则例外),要包含语义,还要应付许多开发场景,有时候确实感觉脑细胞不够用。

这么多年的工作实践让我逐渐有了一套自己的命名习惯,我使用翻译软件的场景也越来越少了,这里分享一下自己的一些命名习惯,希望可以帮到大家。

1.不要使用拼音

下面这样的命名就不要出现了:

.cs-tou {} /* 不建议 */
.cs-hezi {} /* 不建议 */

使用拼音虽然省力,对功能也没有影响,但却是一个比较傻的行为,因为它会让人觉得你比较业余。你自己命名是省力了,但这样的命名对其他同事而言却苦不堪言,因为可读性太差,不符合通常的命名习惯,会导致其他同事一下子反应不过来,例如,.cs-hezi远不如.cs-box一目了然;另外,同一个中文拼音往往可以对应多个不同文字,难以识别。

对于多人合作的项目,一定要注意克己,特立独行并不是用在这种场合中的。

但万事无绝对,如果一些中文类的专属名词和产品没有对应的英文名称,那么可以使用拼音,如weibo、youku等。

2.从HTML标签中寻找灵感

HTML标签本身就是非常好的语义化的短命名,且其数量众多,我们大可直接借鉴。例如:

.cs-module-header {}
.cs-module-body {}
.cs-module-aside {}
.cs-module-main {}
.cs-module-nav {}
.cs-module-section {}
.cs-module-content {}
.cs-module-summary {}
.cs-module-detail {}
.cs-module-option {}
.cs-module-img {}
.cs-module-footer {}

上面的header到footer全部都是原生HTML标签,直接使用它们。这些命名可以与HTML标签不一一对应,例如:

<p class="cs-module-detail">详细内容……</p>

虽然命名中的关键字用的是detail,但我们可以不使用<detail>元素而使用<p>元素,甚至使用<div>元素也可以。类名选择器和标签选择器不同,其可以无视标签,直达语义本身,更加灵活,因此,我们可以进一步放开思维。例如,对于列表,就算不是用的<li>标签,我们也可以在命名的时候使用li,例如一个下拉菜单。为了更简洁的HTML代码,同时兼顾键盘等设备的无障碍访问,可以采用下面的HTML结构:

<div class="cs-module-ul" role="listbox">
<a href class="cs-module-li" role="option">菜单内容1</a>
<a href class="cs-module-li" role="option">菜单内容2</a>
<a href class="cs-module-li" role="option">菜单内容3</a>
<a href class="cs-module-li" role="option">菜单内容4</a>
<a href class="cs-module-li" role="option">菜单内容5</a>
</div>

对于列表想必很多人会使用list,对于链接,很多人会使用link,它们都是很好的命名,不过下次大家不妨直接尝试使用li和a,说不定你会喜欢上这种更加精悍的基于HTML语义的命名:

.cs-module-li {} /* 列表 */
.cs-module-a {} /* 链接 */

我还会从其他XML语言中寻找命名灵感,例如SVG,对于“组”,我会直接使用g,而不是group,这就是因为我借鉴了SVG中的<g>元素;对于“描述”,我会直接使用desc,而不是description,这也是因为我借鉴了SVG中的<desc>元素。

.cs-module-g {} /* 组 */
.cs-module-desc {} /* 描述 */

最后提供一点“私货”,供大家参考。对于一些大的容器盒子或者组件盒子,我现在已经不使用box这个词了,而直接用一个字母x代替,也就是:

.cs-module-x {} /* module容器盒子 */

这样做的原因有3个。

(1)多年的实践让我发现,所有这些常用的单词里面带有字母x的也就box这一个单词,直接使用x代替整个单词不会发生冲突,也容易记忆。
(2)box是一个超高频出现的命名单词,使用一个字母x代替单词box可以节约代码量。例如,在某微博个人主页的CSS中搜索box,结果多达471个匹配,我们大致计算一下,每一个box字符替换成x字符可以节约2字节,单这个CSS文件就可以节约942字节,将近1KB,而一个CSS类名必然会在HTML代码中至少使用一次,也就意味着至少可以节约2KB。
(3)字母x的结构上下左右均对称,每次写完,心里面都会非常舒畅,你会对这个字母上瘾。

3.从HTML特定属性值中寻找灵感

表单元素多使用type属性进行区分,于是这类控件会直接采用标准的type属性值进行命名。例如:

.cs-radio {}
.cs-checkbox {}
.cs-range {}

其他一些属性值也可以用在对应内容的呈现上。例如,下面这些都是非常好的命名:

.cs-tspan-email {}
.cs-tspan-number {}
.cs-tspan-color {}
.cs-tspan-tel {}
.cs-tspan-date {}
.cs-tspan-url {}
.cs-tspan-time {}
.cs-tspan-file {}

无障碍访问相关的role属性也有很多语义化的属性值可供我们使用。例如,下面这些都是非常好的命名,可以牢记在心:

.cs-grid {}
.cs-grid-cell {}
.cs-log {}
.cs-menu {}
.cs-menu-bar {}
.cs-menu-item {}
.cs-region {}
.cs-row {}
.cs-slider {}
.cs-tab {}
.cs-tab-list {}
.cs-tab-panel {}
.cs-tooltip {}
.cs-tree {}

4.从CSS伪类和HTML布尔属性中寻找灵感

我们还可以借鉴CSS伪类以及部分HTML布尔属性的命名作为状态管理类名,例如:

激活状态状态管理类名.active源自伪类:active;
禁用状态状态管理类名.disabled源自伪类:disabled或HTML disabled属性;
列表选中状态状态管理类名.selected源自HTML selected属性;
选中状态状态管理类名.checked源自伪类:checked或HTML checked属性;
出错状态状态管理类名.invalid源自伪类:invalid。

激活状态和选中状态本质上是类似的,其中,对于.checked和.selected,我只会在模拟对应表单控件的场景下使用它们,其余情况下都是使用.active代替,基本上,80%的状态类名都是.active类名。.disabled用来表示案例或元素的禁用状态,比较常用。.invalid只会用在表单校验出错时使元素高亮显示,不算常用。可以看到这里的状态类名都是单命名。

大发贱志
  • 本文由 发表于 2021年8月5日19:31:48
  • 转载请务必保留本文链接:https://bigfa.com/3059.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: