CSS的工程化 + CSS 方法论

CSS的工程化 + CSS 方法论

CSS 是 Web 开发中不可或缺的一部分,在前端工程化不断进步的今天,一方面, CSS 特性随着规范的升级越来越丰富,另一方面,前端业务复杂性性的增加带来的工程愈加庞大,驱使着开发者不断寻找 CSS 工程化的最佳实践。因此,CSS 工程化就顺应而生,主要是为了解决在使用标准 CSS 时遇到的各种难点。

在使用CSS时遇到的难点

  • class命名难,经常把各种名字都想了个遍,为了不重复也是很拼了。
  • 选择器的全局污染问题,经常改了一个地方的样式,影响到很多地方。
  • 样式难以复用,没有办法像 JS 的函数一样,写一次,然后多个地方可以调用,经常有这种我不是代码的生产者,我只是代码的搬运工的感觉。
  • 难以维护,在接手一个旧项目的时候,看到代码中一堆的 !important 或者各种语义不详的类名,这时是不是很想把上一个写这段代码的人拉出来批斗一番呢。


为了让我们在开发的过程中能保持愉悦的心情,当然更重要的是提高开发效率,拒绝无止尽的 Ctrl + CCtrl + V ,也为了同事之间的关系能更加融洽,所以 CSS 的工程化迫在眉睫。
下面将介绍在 CSS 工程化的道路上都提出了哪些解决方案。

预处理器

前面也提到了 CSS 难以复用的问题,另外如果你想给下面这段 HTML 添加样式。

<div class="box-container">
  <div class="boxs">
    <div class="box-item">
      <span class="item"></span>
    </div>
  </div>
</div>

你有两个选择:
第一:一层套一层,像俄罗斯套娃一样,这样写有个好处就是可以一定程度上解决样式全局污染的问题,你只需要修改一下 box-container 就可以开启新一波的套娃旅程了。但是这样写出来的代码不仅可读性差,而且也很冗余。

.box-container {
  color: red;
}
.box-container .boxs {
  color: orange;
}
.box-container .boxs .box-item {
  color: yellow;
}
.box .boxs .box-item .item {
  color: green;
}

第二:像下面代码所示。优点很明显,摆脱了冗余的代码,代码看起来也清晰很多。一开始这样写确实很爽,但是一旦项目大了起来,你就会开始会为起名字而感到烦恼,一不小心起了个重复的名字,这时灾难便开始了,各种样式开始互相覆盖,不得不起更多的名字来覆盖前面的样式。

.box-container {
  color: red;
}
.boxs {
  color: orange;
}
.box-item {
  color: yellow;
}
.item {
  color: green;
}

很明显,上面的两种方案都不能给我们一个良好的编码体验,为了解决 CSS 的这个问题以及编码不灵活等问题,于是出现了 CSS 预处理器,目前比较流行的 CSS 预处理器有 Sass、Less 和 Stylus。


那么什么是 CSS 预处理器呢?
定义:CSS 预处理器定义了一种新的语言,其基本思想是,用一种专门的编程语言,为 CSS 增加了一些编程的特性,将 CSS 作为目标生成文件,然后开发者就只要使用这种语言进行编码工作。
简单的说:开发者可以用特定的编程语言编写代码,然后由预处理器帮你将写好的代码编译成 CSS 。


预处理器增强了 CSS 的语法。让标准 CSS 具备了以下的这些能力。

  • 变量
  • 混合(Mixin)Extend
  • 嵌套规则
  • 运算
  • 函数
  • Namespaces & Accessors(命名空间和访问器)
  • scope
  • 注释

只看这些乏味的说明,是体会不到预处理器可以给我们带来怎样的便利的。下面介绍最常见的三种预处理器,然后你可以从中选择适合你的预处理器来实践一下。相信一旦用习惯了你就会爱上它~

Sass

2007年诞生。最早也是最成熟的 CSS 预处理器。基于 Ruby,和 CSS 编写规范有一定的出入,有学习成本,以 .sass 后缀为扩展名。SCSS 是 Sass 3 引入新的语法,其语法完全兼容 CSS3,并且继承了 Sass 的强大功能,因此也更加容易上手,以 .scss 后缀为扩展名。其语法支持变量、选择器嵌套、继承(extend)、混合(mixin)和一些逻辑语句,同时还支持跨文件的导入功能,因而使得开发者能够很好地使用编程思想书写样式。


关于 **dart-sass **与 **node-sass **的选择?
node-sass 是用 node(调用 cpp 编写的 libsass)来编译 sass,网络原因容易安装失败。
dart-sass 是用 dart VM 来编译 sass,同时也是官方主推。


由于考虑到篇幅过长,不详细介绍如何使用 sass 以及下面提到的预处理器,有兴趣的可以自行学习哈。


使用 Dart Sass 代替 Node Sass
Ruby Sass

Less

2009年诞生。受 Sass 的影响较大,但又使用 CSS 的语法,让大部分开发者和设计师更容易上手。 Less 相对于 Sass 的优点在于十分的轻量,也完全兼容 CSS, 但另一方面可编程能力不如Sass,Bootstrap4 的 CSS 预处理器也从 Less 换成 Sass。


Less介绍及其与Sass的差异

Stylus

2010年诞生。来源于 Node 社区。主要用来给 Node 项目进行 CSS 预处理支持。


stylus中文版参考文档之综述

后处理器

既然预处理器这么优秀,为什么还需要后处理器?
当我们要使用预处理器的时候,还需要专门去学习一下预处理器的语法,而后处理器是面向标准 CSS 的,把兼容性、优化交给后处理器自动完成,学习成本更低。
后处理器是对原生 CSS 进行处理并最终生成 CSS 的预处理器,广义上还是个预处理器,与上面提到的预处理器不同的是,它处理的对象是标准 CSS。
实现原理:

  1. 将标准 CSS 解析,获得AST。
  2. 对 AST 进行后处理。
  3. 将 AST 转换为 CSS 代码。

优点:使用 CSS 语法,容易进行模块化,贴近 CSS 的未来标准。
缺点:逻辑处理能力有限。


比较典型的后处理工具有以下几种。

  • clean-css:压缩CSS
  • AutoPrefixer:自动添加 CSS3 属性各浏览器的前缀
  • Rework:它在 2012年9月 发布第一个版本,由 Stylus 的作者 TJ Holowaychuk 开启。是一个高效、简单、易扩展并且模块化的 CSS 预处理器。

PostCSS

PostCSS 的第一个版本发布于 2013年11月 ,是从 AutoPrefixer 项目中抽象出来的框架。
它本身并不对 CSS 做具体的业务操作,只是将 CSS 解析成抽象语法树(AST),样式的操作由之后运行的插件系统完成。所以说,PostCSS 既不属于预处理器也不属于后处理器。它需要一个插件系统才能发挥作用。我们可以通过“插件”来传递 AST ,然后再把AST转换成一个串,最后再输出到目标文件中去。


image.png
PostCSS的优点

  1. 多样化的功能插件,创建了一个生态的插件系统;
  2. 根据你需要的特性进行模块化
  3. 快速编译
  4. 创建自己的插件,且具可访问性
  5. 可以像普通的 CSS 一样使用它
  6. 不依赖于任何预处理器就具备创建一个库的能力
  7. 可以与许多流行构建工具无缝部署


常用的插件有:

当然,PostCSS 的解析并不局限于 CSS ,结合它提供的自定义语法解析接口,完全可以定义自己的语法。其实类似于 postcss-scss 的插件社区已经有很多了,使用这些插件,可以将原来基于 Sass、Less 等预处理器的代码迁移至 PostCSS。相对于传统的预处理器,PostCSS 这种开放平台型的体系,不拘束开发者的开发方式,同时也促进了更多对于 CSS 解决方案的探索。


PostCSS与 Sass、Less 等预处理器并不冲突。常用的解决方案是 预处理器 + PostCSS 的方案,当然也可以使用 PostCSS 来替代预处理器。


相关链接:



虽然SASS、LESS、Stylus等预处理器实现了 CSS 的文件拆分,但没有解决CSS模块化的一个重要问题:选择器的全局污染问题。按道理,一个模块化的文件应该要隐藏内部作用域,只暴露少量接口给使用者。而按照目前预处理器的方式,导入一个 CSS 模块后,已存在的样式有被覆盖的风险。
于是从工具层面,社区又创造出 Shadow DOM 、 CSS in JS 和 CSS Modules 三种解决方案。

  • Shadow Dom 是 Web Components 的标准。它能解决全局污染问题,但目前很多浏览器不兼容,对我们来说还很久远;
  • CSS in JS 是彻底抛弃 CSS ,使用 JS 或 JSON 来写样式。这种方式很激进,不能利用现有的 CSS 技术,而且处理伪类等问题比较困难;
  • CSS Modules 仍然使用 CSS ,只是让 JS 来管理依赖。它能够最大化地结合 CSS 生态和 JS 模块化能力,目前来看是最好的解决方案。 Vue 的 scoped style 也算是一种。

CSS in JS

彻底抛弃 CSS ,使用 JS 或 JSON 来写样式。是2014年提出的一种方法。 Radium , jsxstyle , react-style 属于这一类。
它的观点是:不在单独的样式表中定义 CSS 样式,而是直接在每个组件中定义。
CSS in JS 的目标是能够用 HTML / JS / CSS 封装的“硬边界”定义组件,使得每个组件中的 CSS 不会影响任何其他组件。
优点:能给 CSS 提供 JS 同样强大的模块化能力。
缺点:不能利用成熟的CSS预处理器(或后处理器)Sass/Less/PostCSS,:hover:active 伪类处理起来复杂。
可以分成两类:
一类还写我们熟悉的CSS语法,比如 styled-compnents

import React from 'react'
import styled from 'styled-components'
const Header = styled.header`
  color: #333;
  font-size: 3rem;
`
export default () => <Header>this is heading</Header>

另一类则不再写 CSS 语法,而要把 font-size 写成 fontSize ,比如 jsxstyle

import React from 'react'
import { Block } from 'jsxstyle'
export default () => <Block component='header' color='#333' fontSize='3rem'>this is heading</Block>

CSS Modules

CSS 的规则都是全局的,任何一个组件的样式规则,都对整个页面有效。
产生局部作用域的唯一方法,就是使用一个独一无二的 class 的名字,不会与其他选择器重名。这就是 CSS Modules 的做法。


来看看一个简单的CSS Modules的例子:

// example.css

.article {
	font-size: 14px;
}
.title {
  font-size: 20px;
}

之后,将这些命名打乱:

.zxcvb {
  font-size: 14px;
}
.zxcva {
  font-size: 20px;
}

将之命名对应的内容,放入到JSON文件中去:

{
  "article": "zxcvb",
  "title": "zxcva"
}

之后,在js文件中运用:

import style from 'style.json';

class Example extends Component {
  render() {
    return (
      <div classname={style.article}>
      	<div classname={style.title}></div>
			</div>
		)
	}
}

这样子,就描绘出了一幅 CSS Modules 的原型。我们需要自动化的插件帮助我们完成这一个过程。


CSS Modules 不是一个官方的规范,也不是浏览器的一种机制,它是一种构建步骤中的一个进程。(构建通常需要 webpack 或者 browserify 的帮助)。通过构建工具的帮助,可以将 class 的名字或者选择器的名字作用域化。


CSS Modules 提供各种插件,支持不同的构建工具。本文使用的是 Webpack 的 css-loader 插件,因为它对 CSS Modules 的支持最好,而且很容易使用。


CSS Modules实现了:

  • 所有样式都是局部化,解决了命名冲突和全局污染的问题
  • class 名的生成规则配置灵活,可以以此来压缩 class 名
  • 只需引用组件的 JavaScript,就能搞定组件所有的 JavaScript 和 CSS

CSS架构

也可以说是 CSS 中的一些设计模式,遵循这些设计模式,能让你的 HTML 与 CSS 更好的解耦,抽取样式中可复用的部分,使你的 HTML 代码更具语义。让你的 CSS 代码更加的可维护,同时更具模块化。
良好的 CSS 架构应该具备以下几个特性:

  • 预测
  • 复用
  • 维护
  • 延展

OOCSS

OOCSS(Object-Oriented CSS)即面向对象的CSS。由Nicole Sullivan提出于 2009 年提出。
使用这种结构,开发人员获得可以在不同地方使用的CSS类。
优点:通过复用来减少代码量(DRY原则)。让你的代码更灵活、更具有可复用性、可维护性及可扩展性。
缺点:维护非常困难(复杂)。当你修改某一个具体的元素的样式的时候,大部分情况下,除了修改CSS本身,你还不得不添加更多的标记类。
特点:

  • 使用类名来扩展基础对象
  • 坚持语义化的命名方式

image.png
有以下两大设计原则:


一、分离结构和设计
设计即一些重复的视觉特征,如边框、背景、颜色,分离是为了更多的复用;结构是指元素大小特征,如高度、宽度、边距等等。将对象设置为基本的样式,而如果这个对象存在多种多样的样式,我们通过添加皮肤的方式给他添加样式。

.button{ 
  padding:10px; 
  box-shadow:rgba(0, 0, 0, .5)2px 2px 5px; 
}
.widget{ 
  overflow:auto; 
  box-shadow:rgba(0, 0, 0, .5)2px 2px 5px; 
}

根据此原则,我们需要对公用的皮肤进行提取并分离,如下:

.button{ 
  padding:10px; 
}
.widget{ 
  overflow:auto; 
}
.skin{ 
  box-shadow:rgba(0, 0, 0, .5)2px 2px 5px; 
}

**
二、分离容器和内容
打破容器内元素对于容器的依赖,元素样式应该独立存在。
通常在我们写CSS的时候,我们通常回根据html元素的位置来规定样式,例如:

<div class="nav">
  <ul>
    <li>列表1</li>
    <li>列表2</li>
    <li>列表3</li>
  </ul>
</div>
.nav ul li {...}

而在OOCSS认为,我们的样式不应该根据元素的位置来判断对象的样式。
而是应该给元素定义自己的样式,如:

<div class="nav">
  <ul class="list">
    <li class="list-item">列表1</li>
    <li class="list-item">列表2</li>
    <li class="list-item">列表3</li>
  </ul>
</div>
.list {...}
.list-item {...}

OOCSS的概念介绍

MCSS

多层级的CSS。这种样式写法建议将样式分成多个部分,每个部分称为层(layers)。


image.png

  • 第0层或基础(Zero layer or foundation):负责重置浏览器样式的代码(如:react.css 或者 normalize.css)。
  • 基层(Base layer):包括可重用元素的样式,如 button , input , hints 等等。
  • 项目层(Project layer):包括单独的模块和“上下文”—— 根据用户端浏览器或用于浏览的设备,用户权限等对元素的样式进行调整。
  • 装饰层(Cosmetic layer):使用OOCSS风格来书写样式,对元素外观做微小的调整,建议仅留下影响外观的风格,而不能破坏网站的布局(例如颜色和非关键缩进等)。


层与层之间的交互层次是非常重要的:

  • 在基层中定义中性的样式,并且不影响其它层。
  • 基层中的元素只能影响基层的CSS类。
  • 项目层中的元素可以影响基层和项目层。
  • 装饰层是以描述OOCSS类的形式进行设计,不会影响其他CSS代码,而是在标记中有选择的使用。

SMACSS

SMACSS即可扩展和可模块化结构的CSS,该方法的主要目标是减少代码量并简化代码维护。Jonathan Snook 于2011 年提出了这一思想。
与 OOCSS 相比, SMACSS 有更多的细节,但在决定哪些 CSS 规则应该进入哪些类别时仍需要慎重考虑。后来像 BEM 这样的方法避免了做决策的步骤,使其更容易被采用。
它主要是将规则分为5类。


image.png


一、基础(Base)
这些是网站的主要元素的样式,如body,input,button,ul,ol等。在这一步中,我们主要使用HTML标签和属性选择器,在特殊情况下,使用CSS类(如:如果你有JavaScript-Style选择)。

html {
  background-color: #fff;
}

body, form {
  padding: 0;
  margin: 0;
}

a {
  text-decoration: none;
}

h1, h2, h3 {
  margin: 1em 0;
}


二、布局(Layout)
主要是些全局元素,顶部,页脚,边栏等模块的大小。
image.png

#header {
  width: 960px;
  margin: 0 auto;
}

.l-article {
  ...
}
.l-grid {
  ...
}
.l-grid > li {
  ...
}


三、模块(Module)
模块(类似于卡片布局)可以在一个页面中使用多次。避免使用 id 或标记名称做选择器。子模块以原模块名称加 - 作为名称。如:.mod-header , .mod-body
image.png

.modal-body {
  width: 100%;
}
.modal-header {
  height: 50px;
  width: 100%;
}


四、状态(State)
规定了模块各种状态以及网站的基础部分。与 Layout,Module搭配,表示 Layout 或 Module 的状态变化。由 class 定义,命名规则是 .is-* 开头。
image.png

.is-hidden {
  display: none;
}
.is-error {
  font-weight: 700;
  color: #f00;
}
.is-tab-active {
  border-bottom-color: transparent;
}


五、主题(Theme)
设计你可能需要更换的样式。定义网站主视觉,与 Layout 类似,但影响的是网站整体视觉的变化。class 名称通常以 .theme-* 做开头。

Atomic CSS

在 2014 年引入的一种方法。是 Yahoo 提出来的一种独特的 CSS 代码组织方式,应用在 Yahoo 首页和其他产品中。是bootstrap中的工具类。
使用 Atomic CSS,为每个可重用的属性创建单独的CSS类。例如:margis-top: 1px ; 就可以创建一个类似于 mt-1 的 CSS 类,或者 width: 200px ; 对应的 CSS 类为 w-200
这种方法与 OOCSS、 SMACSS 和 BEM 完全相反,它不是将页面上的元素视为可重用的对象,而是完全忽略了这些对象,并使用可重用的单一实用工具类来对每个元素的样式进行设置。
优点:这种样式允许你通过重用声明来最大程度地减少你的 CSS 代码数量,并且也能很轻松的更改模块。写出基于视觉功能的小的,单用途的 CSS 类。
缺点: CSS 类名是属性名称的描述,而不是元素的自然语义。容易使人在开发过程中变得迷茫,开发本身也十分容易复杂化。

image.png

AMCSS

AMCSS是Attribute Modules for CSS的缩写,即针对属性的CSS设计。表示借组HTML属性来进行CSS相关开发。
image.png
传统我们多个模块是通过多个类名进行控制的,典型如下:

<div class="button button-large button-blue">Button</div>

而AMCSS则是基于属性控制,例如:

<div button="large blue">Button</div>

为了避免属性冲突,我们可以加一个统一的前缀,例如 am- ,于是有:

<div am-button="large blue">Button</div>

此技术能够实行离不开一个选择器:[attr~="val"]

BEM

是一种类名命名规则。由 Yandex 团队于 2009 年提出的一种前端命名方法论。饿了么的框架 Element UI 就是 BEM 的一种。
BEM是一种让你可以快速开发网站并对此进行多年维护的技术,能够帮助你在前端开发中实现可复用的组件和代码共享。
优点:

  1. 解决了命名空间的问题
  2. 多人协作时,只要有文档清楚标注规则,后来人可以很轻易的读懂,接手
  3. 更易于维护

缺点:

  1. 容易写得又长又丑
  2. 代码量比较多,没那么简洁
  3. 需要完善的说明文档和规则


Block(块)
我们可以将块理解为web应用中的组件或者模块。header,container,menu,checkbox,input。每个页面都可以看作是多个 Block 组成。
image.png
命名规则:

.button
.text-field
.heading
.menu

**
Element(元素)
是构成 Block 的元素,只有在对应 Block 内部才具有意义,无法独立于 Block 之外。menu item,list item,checkbox caption,head title。
image.png
命名规则:以 Block 名称加上两条底线 __ 作为前缀。

.button__icon
.text-field__label
.heading__title
.menu__item

Modifier(状态)
描述 Block 或 Element 的属性或状态。同一个 Block 或 Element 可以由多个 Modifier。disabled,highlighted,checked,fixed,size big,color red。
image.png
image.png image.png
命名规则:以 Block 或 Element 名称加上一个底线 _ 作为前缀。

.button_active
.text-field_editable
.heading_align_top
.menu__item_promo

BEM的定义
【CSS模块化之路1】使用BEM与命名空间来规范CSS

总结

关于 CSS 的工程化,我认为可以遵循 CSS 架构中的方法,但是靠自觉是无法实现工程化的,所以我们必须借助 PostCSS 中提供的一些插件来规范我们的代码,解放我们的双手。同时再结合具体的情况来考虑是否需要预处理器(如果已经在用了可以继续用,没有的话其实 PostCSS 也可以达到同样的效果),以及 CSS in JS (这个我感觉有点激进哈,有点学习成本)或者 CSS Modules(因为Vue中的scoped也属于这一范畴,我觉得还挺方便的,可以考虑用一下)。但是无论使用什么工具,什么方法,定义良好的样式结构始终是最重要的。


参考文章:

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值