我说 CSS 类名可以缩写,你不要打我!

5,375

认知偏见

当我们遇到一种与自己认知不同的方案或者观点的时候,情感上的喜欢和不喜欢肯定是冲在我们前面的,这是我们的本能无可厚非。你不喜欢一个人,你就很难注意到他身上的优点,你喜欢一个人你本能会忽略这个人身上的缺点。

像最开始我不喜欢 BEM 一样,我觉得它让我的 CSS 名称又长又臭。当我静下心来去思考它要解决的问题时,我知道自己看待问题是如此的带有偏见。

任何一种方案的提出必定是要解决一个痛点。建议可以先把个人喜好滞后,先冷静的思考这个方案要解决什么问题。很有可能这个方案不是不好,只是他没有解决你的痛点,仅此而已。

当然本文是想要给大家分享一个观点,所以还请大家多海涵。😄

你不应该缩写

我们在学习任何一门计算机语言的时候,都是要求命名一定要具有可读性。比如在原生 js中,我们会看到这样的命名:

  • getElementById
  • getElementsByClassName
  • getElementsByName

基本上我们一看就知道这个是要干什么的,可以如果你发现有的人把上面的命名改成这样:

  • getEId
  • getECN
  • getEN

我想你和我一样第一眼看到这样的代码,脑袋里面会出现一个大大的问号,可能 getEId 大概还能猜到是要干什么。但是 getEN 我可能会以为这个是将中文转化成英文的方法吧。

所以大家约定俗成的是,在命名的时候不要用缩写,能写全的都尽量写全,即使那样会增加一定的代码体积,但是对于项目管理来说是值得推荐的。

然而在写 CSS 的时候,我却发现,这个条定律似乎可以稍微的松动一点点。

什么时候我们会选择缩写?

为了不一开始引出结论引起大家的抵触情绪,我先谈谈在什么情况下我们会选择缩写?

  • 生活中:KPI、IOS、CEO、活久见 ...
  • HTML: H1、em、a、br ...
  • JS: var、let、$、_ ...

总结一下,在特定环境中一些出现频率非常高的词组,为了使用方便,我们会选择使用缩写。

还是拿我们之前的 JS 环境举例,像 getElementById 这个方法,在我们实际开发过程中,如果你就只用到 10 次左右,我想你并不会考虑去缩写它的问题。

可是当你发现使用它的频次达到了成百上千次的时候,即使你的编辑器,在你输入get 会为你联想出getElementById,你依然会考虑给这个单词取一个更短的名字。比如像这样:

function $(id){
    return document.getElementById(id);
}

简单的来说,我们想要缩写就是它出现的次数是在是太多了。

语义化 CSS 不应该缩写

在 CSS 这个环境中,对于命名我相信大家听得最多的就是 语义化 这个词了。

对于 CSS 语义化 我比较佩服的就是 禅意花园 了。作者就写了一个 DOM 结构和一些 CSS 类名,然后成百上千的人就能基于他这些 CSS 类名,完全不需要修改任何的 DOM 结构,就能写出和作者样子完全不一样的页面。这就和换皮肤一样,结构和样式的完美分离。

所以这里还是要声明一下,对于语义化的命名,我就非常不推荐使用缩写

那么除了 语义化 还有什么可选的 CSS 命名方式呢?

这个问题我相信大多数的前端开发者还是很难回答的。因为在从业多年之后,我发现一直坚信的 CSS 语义化 是导致我写 CSS 效率低下最根本的原因。因为命名就是一个需要花费很多时间的东西。具体可以看看我之前写的其它 CSS 相关文章 《如何管理 CSS “内裤”》

基于 CSS 类名可以缩写的这个点,我给到的答案是 ACSS (Atomic CSS),当然这里说 ACSS 是一种命名方式是极为不准确的。

如果你目前对于 ACSS 这个理念,表示不太容易接受的话,说明它不能解决你工作中的痛点。就把时间拿去打游戏或者和妹子聊天吧,后面的内容就可以忽略了。

为什么 ACSS 可以缩写

ACSS 就如同它的名字来说是原子化CSS。就是把的 CSS 的样式,拆分到原子级别。

简单的说,就是一个命名只有一行代码,一行代码只表示一个样式,是不是听起来特别符合设计模式中的单一指责?

语义化

.main-title{
    font-size:24px;
    line-height:32px;
    color:$color_primary;
    font-weight:700;
}
<h2 class="main-title">这是主标题</h2>

ACSS

.fontSize24{ font-size:24px; }
.lineHeight32{ line-height:32px; }
.colorPrimary{ color:$color_primary; }
.fontWeight700{ font-weight:700; }
<h2 class="fontSize24 lineHeight32 colorPrimary fontWeight700">这是主标题</h2>

语义化 Class 和 ACSS Class,被复用的概率基本不在是量级的。就拿上面的例子来说,一个页面的 .main-title 个数能达到 10 个就已经是一个很复杂的页面了。即使是在整站来看被复用的次数也是有限的。

ACSS 里面某些类名即使只是在一个页面被复用的概率,都是远高于语义化 Class 。比如说 font-size:14px;在一个项目中被复用上百次是很常见的事情。

所以 ACSS 里面的类名可以缩写的核心在于,ACSS 的类名被复用的次数是在是太多了。

CSS 如何缩写?

类名不能缩写有一个最核心反驳点在于,如何定义缩写的规则。不同的人缩写的习惯是不一样的。

首先我们来看看,知名的一些解决方案对于缩写是如何定义的。

@bootstrap v4

{
    "bg-primary":"background-color:$primary;",
    "p-*":"padding: * ;",
    "m-*":"margin: * ;";
    "d-inline":"display: inline;",
    "sm":"small",
    "md":"middle",
    "col":"column",
    "btn":"button",
    "w-":"width:*;"
    /*...*/
}

@material-ui

{
    "bgcolor":"backgroundColor",
    "zIndex":"z-index",
    "p":"padding",
    "m":"margin",
    "sm":"small",
    "md":"middle"
    /*...*/
}

@张鑫旭老师

{
    "dn":"display:none;",
    "b":"font-weight:bold;",
    "cl":"clear:both;",
    "ovh":"verflow:hidden;",
    "ml10":"margin-left:10px;"
}

相信看到这里,你和我一样是凌乱的。哪些可以缩写啊?是用连字符还是用下滑线还是用大小写啊?

不过我们零乱的原因在于我们不在这些项目组里面。我们没有在 material-ui 的项目里写 padding 写过上百次, 也没有在 bootstrap 项目里写 margin 超过成上千次,更没有像张老师一样有超过 10 年的页面编写经验。所以很容易有一些既定的思维逻辑。

多年的工作经验,我也维护着这样一份枚举映射表。但是我也不得不承认,通过枚举映射的方式,对于新手来说是非常高的。“汝之蜜糖,乙之砒霜” 就是这样一个道理。

为了解决这个问题,我花了很多的时间,进行着尝试,以下是我目前的结论(基本上是在张老师的逻辑上二次封装的)。

@我的方案

  1. 只取首字母: .db{ display:block; };
  2. 有数字直接连接: .mb10{ margin-bottom:10px; };
  3. 短横线表示负数: .mb-10{ margin-bottom:-10px; };
  4. 百分号用 % 表示: .w100\%{ width:100%; };
  5. 小数点用 . 表示: .lh1\.5{ line-height:1.5; };
  6. 连接参数(不允许缩写)用下划线: .c_primary{ color:$primay; };
  7. 对于非 ACSS 不要缩写或大家约定俗成的名称: .clearfix{ };

特殊符号在 CSS 中需要 \ 转义,在html中使用的时候不需要。

和其它枚举映射表最大不同点在于,我的映射表是通过上面这套命名规则维系着的。

简单的说就是记忆上述这套命名规则会比记忆一整套映射表是更容易的。

并且命名规则会减少开发时候疑惑的概率。比如我现在需要一个背景颜色,如果没有命名规则,我就要去看一下映射表里面,是类似于 material 的bgcolor 还是和bootstrap 一样叫 bg-。如果有明确的命名规则你就知道肯定是叫 bc 因为只取首字母。

有一套命名规则,也比较便于其它的人往里面添加常用的类,而不是根据个人的习惯。当然要说服不同的人定义同一套规则也是很难的,对于这个我只能说随缘吧。

<div class="pt16 pl16 pr16 pb16">
  <h3 class="fs16 lh24 fw700 mb4">Atomic css</h3>
  <p class="fs12 lh20">Atomic css 的开发体验真是太好了,速度也是超快。</p>
</div>
<style>
    .card{ padding: 16px; }
    .title{ font-size:16px; line-height:24px; font-weight:700; margin-bottom:4px; }
    .content{ font-size:12px; line-height: 20px; }
</style>
<div class="card">
  <h3 class="title">Atomic css</h3>
  <p class="content">Atomic css 的开发体验真是太好了,速度也是超快。</p>
</div>

当别人还在纠结这个容器是叫 card 还是叫 box 的时候,你的代码已经都写完了。当你熟练整个命名规则之后,在开发过程中你会有种,你想要某个样式,然后你的代码就已经写完了的错觉。这个对于 开发体验 来说是非常好的。

当然我这个方案也不并完美,还是有些边边角角不能完全覆盖,但是基本上日常基础的样式是完全没有问题的。如果你有比我这个规则更少的命名方案,请和我分享。

如果你有使用 emmet 这个插件,你会发现你甚至连编写 css 的属性都可以是使用缩写的然后 tab 补全的。当然我的规则和 emmet 的有些不同,但是不影响实际的使用。

自定义项目的样式库

ACSS 这个映射表和设计同学的原子设计素材有很大的关系,甚至说,应该保持一致。比如说你的项目 A 所有的间距都是基于 5 的倍数开发的,那么你的这份映射表里就应该会有:

.mb5{ margin-bottom: 5px; }
.mb10{ margin-bottom: 10px; }
.pt10{ padding-top:10px; }
.pb10{ padding-bottom: 10px; }
/* ... */

你的项目 B 所有的间距都是基于 4 的倍数开发的,那么你的映射表里就不应该有上面 5 的倍数的逻辑,而是:

.mb4{ margin-bottom: 4px; }
.mb8{ margin-bottom: 8px; }
.pt4{ padding-top:4px; }
.pb8{ padding-bottom: 8px; }
/* ... */

如果你觉得每次新项目都要去写这个很麻烦,可以推荐我们这边封装的 Mixin 库,less 和 sass 都有 sacss

对于了解 style-system 的朋友可能会发现,对于间距我们并没有采用类似于 style-system 中倍数逻辑( material-ui 和 bootstrap 是采用的这个逻辑 )。

$margins: [4px,8px,16px,24px,32px];
.mb-1{ margin-bottom:$margins[1]; }
.mb-2{ margin-bottom:$margins[2]; }
.mb-3{ margin-bottom:$margins[3]; }
.mb-4{ margin-bottom:$margins[4]; }
/*...*/

简单的说,我们觉得这个很不直观。并且如果我们要增加一个 20px 的间距,我们的整个逻辑就会出现问题之前叫 mb-3的类名要换成 mb-4(当然 style-system 的逻辑应该是先定义好设计规范再开发,不应该在后期添加不符合设计规范的间距)。

然而实际往往是计划赶不上变化,所以还不如直接用数字来的直接简单粗暴,在开发的还不用思考。mb4就是 margin-bottom:4px; 不是什么 margin-bottom:$margins[4],你还不知道这个$margins 数组里面是啥。

样式不止 CSS

本文虽然一直是在围绕着 CSS 展开的,但其实我想说的其实是 样式可以尝试考虑 ACSS, 然后 ACSS 的地方可以尝试考虑 缩写 这个点。

样式写多了的同学会知道,常用样式的来来去去也就那几个,无非就是调整一下他们的排列组合。每次写这些重复的样式代码我就感觉自己是在重复造轮子。

随着 JS 疯一样的发展,CSS-in-js 也正发热发光。但这并不影响我们这整套逻辑。这里拿比较有名的 emotion 举例。

import react from 'react';
import "acss.scss";

render (<h2 className="fs24 lh32 c_primary fw700">{children}</h2>);
import react from 'react';
import { css, jsx } from '@emotion/core';
const primary = 'blue';

render(
<h2 className={css`
    font-size:24px;
    line-height:32px;
    color:${primary};
    font-weight:700;
`}>这是主标题</h2>
);

我们甚至用原生的 CSS 就解决了 CSS-in-js 要解决的问题(对于这个点我就不展开了,大家可以思考以下为啥CSS-in-js 这样的理论刚好在组件化风行的时候出现?)。

如果你想在 React-native 中也使用以上的开发体验。可以去看一下我们这边针对于 React-native 开发的 ACSS 组件 @sacss/react-native

import React from 'react';
import {Text, View, ImageBackground, Image} from './packages/withAcss';
import mImg1 from "./assets/1.jpg";
import mImg2 from "./assets/2.jpg";

export default function App() {
    return (
        <View bc="#f5f5f5" pt={100} ph={30} h="100%">
            <View mb={16} aic>
                <Image br={32} w={64} h={64} source={mImg1}/>
            </View>
            <Text c="#ffffff" p={16} bc="#4c5fe2" tac mb={16}>Hello world!</Text>
            <ImageBackground source={mImg2} mb={40}>
                <View bc="rgba(255,255,255,.8)">
                    <Text fs={40} lh={100} tac fwb c="#4c5fe2">Hello Again!</Text>
                </View>
            </ImageBackground>
        </View>
    );
};

想要实现上面的效果,只需要这几行代码,你可以对比以下之前不管是用styleSheets 还是用 style-components 的代码量。就知道这个是多么的酣畅淋漓。

最后

本文一开始虽然只是想解释,CSS 类名可以缩写这一个点。但到最后还是不免我介绍了我钟爱的 ACSS,并王婆卖瓜的推荐了三个工具。毕竟是花了自己很多心力,还是希望能被更多人看到和使用。

本文观点属于经验总结,走的是务实的逻辑,可能实现方案看起来有点丑陋。

但是不能解决问题,优雅又有何用呢?有什么疏漏,还请大家海涵,可以给我留言指正。

解决方案

更新于 2021/02/21

SACSS: Static Atomic CSS

npm package jsdelivr github

讲了这么多,感觉都只是空谈理论。这边我多年的使用经验,总结的一个 ACSS 的 npm 类库 SACSS 供大家使用。

对于 ACSS, bootstrap, material-ui, github ... 都有相关的 类库,然后整个 ** 类库** 最完善的属于 tailwindcss。当然他们都是基于 style-system 理论创建的。上手成本相对较高,且往往需要设计师的介入。

和这些项目相比,我这边的方案,优点在于简单和极致的 CSS 开发体验。简单到整个逻辑只落点在命名规则,看完文档 5 分钟就会用,甚至完全理解所有的逻辑。极致的 CSS 开发体验,体现在熟悉这套规则之后,你会开始怀疑,你的手指速度慢于你思考 CSS 的速度。

当然为了追求简单和开发体验这也是缺点,就是不够完善,没有处理类似 hover,focus... 等中间态,也没有添加任何自定义的响应点。这部分需要大家基于自己项目和 命名规则 自由扩展。