BEM、OOCSS、ITCSS...

    2413
    最后修改于

    本文在概述的基础上,进一步介绍 BEM、OOCSS、ITCSS 这些用于组织 pureCSS 的范式,也即是 CSS 架构。

    尽管习惯上这几种范式一般会与 SCSS/LESS 等预处理器结合使用,但是在概念上,实际上是一种组织 CSS,避免 CSS 变得混乱的方法论(CSS 架构)。SCSS/LESS 更像是高效书写标准 CSS 的方言。

    OOCSS#

    面向对象的 CSS,推崇结构与样式分离,容器与内容分离。咋一看,这句话非常抽象,还是结合场景来看看。

    结构与样式分离#

    比如一个 Login 页面,有两个按钮,login-buttonregister-button,如果不考虑复用,一次性实现,可能是这样的。

    css
    .login-button {
    	background-color: blue;
    	color: white;
    	padding: 10px 20px;
    	border-radius: 5px;
    }
    
    .register-button {
    	background-color: red;
    	color: white;
    	padding: 10px 20px;
    	border-radius: 5px;
    }
    

    这么看似乎也没啥问题。但是如果在一个大型网页中考虑,有很多个button,每一个button都类似,但是不完全一样。这种情况下,一次性实现很快会导致 CSS 变得混乱。

    OOCSS 认为,每个 button 有相同的共性。将相同的部分抽离出来,作为一个btn类(结构),然后每个 button 独特的样式,抽离出来作为单独的样式类,如 btn-primary(样式)。

    css
    /* 结构类:定义基本结构,相同的部分 */
    .btn {
    	padding: 10px 20px;
    	border-radius: 5px;
    }
    /* 样式类 */
    .btn-primary {
    	background-color: blue;
    	color: white;
    }
    /* 样式类 */
    .btn-secondary {
    	background-color: red;
    	color: white;
    }
    

    使用时,通过组合不同的 css 类,.btn .btn-primary来实现最终的样式。此为结构与样式分离。

    容器与内容分离#

    在如下的场景中,一个博客列表,一种有毛病的写法是这样的。

    html
    <style>
    .blog-list {
      width: 100%;
      max-width: 800px;
      margin: 0 auto;
    }
    
    .blog-sidebar-list {
      width: 100%;
      max-width: 200px;
      margin: 0 auto;
    }
    
    .blog-list .blog-list-item {
      border: 1px solid #e0e0e0;
      padding: 15px;
      margin-bottom: 15px;
    }
    .blog-list .blog-list-item p {
      color: #666;
      line-height: 1.6;
    }
    </style>
    
    <div class="blog-list">
      <div class="blog-list-item">
        <h2>文章标题</h2>
        <p>文章内容...</p>
      </div>
    </div>
    <div class="blog-sidebar-list">
      <div class="blog-list-item">
        <h2>文章标题</h2>
        <p>文章内容...</p>
      </div>
    </div>
    

    典型的问题比如 .blog-list .blog-list-item p这个选择器,pblog-list 组合在一起,是一种不理想的情况。因为 blog-list-item 也可能放在 blog-sidebar-list 中,这时候这个选择器就不起作用了。
    很自然的解决方案就是移除选择器中的 .blog-list

    css
    /* 移除选择器中的 .blog-list */
    .blog-list-item {
      border: 1px solid #e0e0e0;
      padding: 15px;
      margin-bottom: 15px;
    }
    .blog-list-item p {
      color: #666;
      line-height: 1.6;
    }
    

    .blog-list-item作为内容,让.blog-list作为容器,两者拆分。使得 .blog-list-item 也能放在.blog-sidebar-list 中。这也就是所谓的容器与内容分离。鼓励有意识的区分容器和内容,让容器和内容的样式相互隔离,互不影响。

    是的,这的看起就像是讲正确的废话,在经过长时间的磨练之后,这更像是一种会自然而然形成的技巧。在我的视角看来,OOCSS 更多的是实现思路上的指导意义,而非某种具体的解决方案。CSS 实现同一样式的路径很多,但这是几种较优的路子之一,它就像是在说 CSS 本就应该如此,选它吧,不会有啥问题。

    代表性框架比如 Bootstrap(宽泛一点讲,大部分 CSS 框架实际上都符合 OOCSS 的理念)

    BEM#

    BEM — Block Element Modifier
    类似于 OOCSS,BEM(Block-Element-Modifier)也是一种范式,提倡有意识区分的BlockElement以及Modifier

    • Block 是指页面中的一个区块,比如Header,Container,Menu
    • Element 即是指区块中的元素,比如menu-item,list-item等。
    • Modifier 则是修饰符,用来修饰区块 / 元素的状态,比如isActive, isDisabled
      CSS 类名通过 Block__Element--Modifier 的方式进行组合。
      一种示例如下
    html
    <div class="card">
    	<div class="card__content">
    		<h3 class="card__title">Card Title</h3>
    		<p class="card__text">Card text</p>
    	</div>
    	<button class="card__button">Button</button>
    	<button class="card__button card__button--primary">
    		Primary Button
    	</button>
    </div>
    <style>
    /* Block Style */
    .card {}
    
    /* Elements Style */
    .card__content {}
    .card__title {}
    .card__text {}
    .card__button {}
    
    /* Modifier Style */
    .card__button--primary {}
    </style>
    

    可以发现,BEM 跟 OOCSS 并不冲突,并且说的很像是一回事。
    我认为对于Block,实际上也就是 OOCSS 中所谓的容器,Element实际上也就是 OOCSS 的内容(容器与内容分离)。只是换了一种说法。而 Modifer 则是一种另类的样式(OOCSS 结构与样式分离)。
    区别在于 BEM 推崇一种看起来比较另类的Block__Element--Modifier的命名方式,不过这种组织 CSS 方式很好的与 SCSS/LESS 这样的预处理工具结合在一起,反倒是进一步提高了效率。

    SMACSS#

    SMACSS(Scalable and Modular Architecture) 将 CSS 规则进行分类,分为 Base、Layout、Module、State 和 Theme 五种类型。

    Base#

    基本规则,定位类似于用户代理样式表。定义元素的默认值,例如重置样式、排版和其他全局样式。它们不应包含类或 ID。

    css
    html, body, ul, ol {
    	margin: 0;
    	padding: 0;
    }
    
    Layout#

    布局规则控制页面上各个区块的组织方式如结构、排列。这些规则通常应用于主要组件,如headerfootermain section

    css
    /* Layout */
    .l-container {}
    .l-header {}
    .l-footer {}
    
    Module#

    模块是应用程序的可复用的组件(如按钮、表单、卡片)。它们应相对独立,在整个项目中易于迁移和复用。

    css
    /* Module */ 
    .m-button {}
    .m-card {}
    
    State#

    状态规则用于修饰模块或布局,表示所修饰组件所处的状态(类似于 BEM 中的 Modifier)。

    css
    /* State */
    .is-active {}
    .has-error {}
    
    Theme#

    主题规则定义页面的不同主题或外观的样式。它们是可选的,仅当项目需要多个主题时才需要。

    css
    /* Theme */
    .t-dark .m-button { }
    

    下面是一个 SMACSS 的 HTML 示例:

    html
    <div class="l-container">
    	<header class="l-header"><!-- content --></header>
    	<main>
    		<button class="m-button is-active">Click me</button>
    	</main>
    	<footer class="l-footer"><!-- content --></footer>
    </div>
    

    ITCSS#

    ITCSS: Scalable and Maintainable CSS Architecture - Xfive
    相比于 BEM 和 OOCSS,ITCSS(Inverted Triangle) 则没有那么高的使用率,但满意率则是比较高的(stateofcss 2019/2020)。但是跟 SMACSS 很像。

    它根据 CSS 的特异性按特定顺序组织 CSS,将 CSS 分为七层,得到一个清晰的层次结构,防止冲突从而使代码更易于维护。
    自下而上逐级增加抽象程度。

    ITCSS 分为七层:

    1. 设置:全局配置和变量,例如颜色、字体和断点。
    2. 工具(Tools):整个项目中使用的混合和函数(在 SCSS 等预处理器中为 CSS 增加了 编程特性,从而实现更好的复用)。
    3. 通用:高层次、低特异性的样式,定位类似于用户代理样式表,提供一个默认值,抹平样式差异,如 resets 和 normalizations。
    4. 元素:HTML 元素的默认样式,例如titleaul
    5. 对象:与布局相关的样式,例如网格系统和容器。
    6. 组件:可重用的 UI 组件,例如按钮、卡片和表单。
    7. Utilities:场景特定但实用的辅助类,例如间距和可见性。

    看起来会发现,ITCSSSMACSS 很像,elementsgeneric实际上就是 SMASS 的base类,settings对应theme类,objectcomponent则分别对应 SMASS 的layoutmodule
    但是缺少state层的定位,多了utilities层。此外还有一个面向预处理器的Tools层。
    可以视为对 SMACSS 的一种改进。

    html
    <style>
    /* Settings */
    :root {
    --primary-color: #007bff;
    --secondary-color: #6c757d;
    --font-family-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif;
    }
    /* Tools used with SCSS */
    @mixin clearfix {
    	&::after { 
    		content: "";
    		display: table;
    		clear: both;
    	}
    }
    /* Generic */
    *, *::before, *::after {
    	box-sizing: border-box;
    }
    /* Elements */
    h1, h2, h3, h4, h5, h6 {
    	font-family: var(--font-family-sans-serif);
    	font-weight: bold;
    }
    /* Objects */
    .o-container {
    	max-width: 1200px;
    	margin: 0 auto;
    	padding: 0 15px;
    }
    /* Components */
    .c-button {
    	display: inline-block;
    	padding: 10px 20px;
    	background-color: var(--primary-color);
    	color: #fff;
    }
    /* Utilities */
    .u-text-center {
    	text-align: center;
    }
    </style>
    
    <div class="o-container">
    	<header> <!-- ... --> </header>
    	<main> <button class="c-button">Click me</button> </main>
    	<footer> <!-- ... --> </footer>
    </div>
    

    Atomic CSS#

    原子化 CSS(也称为函数式 CSS)越来越受欢迎,tailwind 大行其道。相比于上面各种组织 CSS 的范式,原子化 CSS 就如同一个逆子,反其道而行之。

    通过为每个 CSS 属性创建一个 CSS 规则(进而创建唯一的类名),从而减少已定义规则总数的方法,从而实现大型样式重用。
    比如对于background-color: red;这一个属性,可以创建一个 bg-red 的 css 类。除此之外,再无其他。
    这样的好处是:

    • 使用到的 CSS 的属性是有限的,从而将项目的总 CSS 规则限制在一个可接受的范围内。(CSS 文件体积缩减)
    • 规避选择器的问题,将样式重新与 HTML 关联在一起,而不用担心选择器的命名、范围等一系列问题,查看 HTML 结构的同时也可以知道大致的样式
    • 简单、准入门槛低,更容易被不精通 CSS 的人理解。
      以 tailwind 首页的 example 为例,非常的暴力有效,在组件驱动开发场景下相对更契合。
    html
    <figure class="md:flex bg-slate-100 rounded-xl p-8 md:p-0 dark:bg-slate-800">
    	<img class="w-24 h-24 md:w-48 md:h-auto md:rounded-none rounded-full mx-auto" src="/sarah-dayan.jpg" alt="" width="384" height="512">
    	<div class="pt-6 md:p-8 text-center md:text-left space-y-4">
    		<blockquote>
    			<p class="text-lg font-medium">“Tailwind CSS is the only framework that I've seen scale on large teams. It’s easy to customize, adapts to any design, and the build size is tiny.”</p>
    		</blockquote>
    		<figcaption class="font-medium">
    			<div class="text-sky-500 dark:text-sky-400"> Sarah Dayan </div>
    			<div class="text-slate-700 dark:text-slate-500">
    				Staff Engineer, Algolia
    			</div>
    		</figcaption>
    	</div>
    </figure>
    

    小结#

    了解完上面这些 “CSS 架构” 之后,不难发现,不同的 CSS 架构之间并不冲突,更多像是对同一问题在不同视角下提供的解决思路,对问题的不同划分,实际上可以相互配合。

    • 🥳0
    • 👍0
    • 💩0
    • 🤩0