作者:鱼头的Web海洋 公号 / 陈大鱼头 (本文来自作者投稿)
原文地址:https://mp.weixin.qq.com/s/pPtzhXbAaXfoTV2q3u0Dww 最近听说TypeScript3.7添加了对Optional Chaining[2]的支持,然后就想着给鱼头的脚手架ying-template[3]的TS版本升级,然后在命令行发现这样的一句信息: 'postcss-cssnext' 已经被 'postcss-preset-env'代替了。详情请查看 https://moox.io/blog/deprecating-cssnext/ 其实鱼头的脚手架里早就把postcss-cssnext换成了postcss-preset-env`,不过一直没删,但是看到这句话之后,处于好奇,就去翻了翻PostCSS的官网,然后又思考了下这些年CSS的发展历程,遂有这篇文章的出炉。

浅谈现代化的CSS

从1997年 CSS1.0 发布到如今,从最开始只支持简单的文字排版到如今已经可以做出酷炫的3D动画,CSS已经走过了22个年头,其发展如图所示:
null
[图片来自MDN[4]]
随着互联网的发展,人们对网页的要求已经是从只要展示图文就好变成了各种交互跟视觉效果都需要有着更多的体验要求。CSS为此也是不断的更新着。
随着web业务日益复杂化和多元化,前端开发也从单纯的web page转变成web app,在此也诞生了“前端工程化”的概念,一个完备的web app往往会很大很复杂,甚至会有很多人共同维护,以往的拼页面,写jQuery已经是不足以支撑现代的需求。同样的,CSS也是如此,不再是内联写几个marginpadding或者HTML一股脑引入几个CSS就足够的,而且由于人员配置的增多,不同的开发,命名习惯,样式是否会冲突也是必须要考虑的。
除了工程问题,还有就是CSS与浏览器之间的关系也是我们不得不考虑的,虽然CSS发展的很快,但是浏览器对CSS新特性支持的进度确实非常缓慢的。所以虽然某些属性已经推出了很多年,但是也往往因为浏览器的原因而无法进行大规模的使用。
虽然在实际开发过程中,CSS有着这样那样让人无法忽略的问题,但是“方法总比困难多”,在前端界也有许多热心的大牛们在尝试着解决这些问题。这次让鱼头与大家一起分享下这些与CSS相关的技巧与方法。

最初的CSS模块化 —— CSS命名规则

命名一直是开发者比较头疼的问题,在前端里,除了JS各种变量的命名,还有元素class的命名,虽然我们可以随意起名,愿意的话甚至可以使用.a .b .c等无意义的规则来命名,但是如果是一个长期的,大型的或多人协作的项目里这么命名,恐怕容易被人胖揍。这次我们来分享下业界常用的用来防挨揍的命名规则。

OOCSS(Object-Oriented CSS)

OOCSS有两个编写原则:
•结构与样式分离•容器与内容分离
我们来看看官网的一个例子:
null

1
2
3
4
5
6
7
8
9
10
11
12
<div class="mod grab">
<b class="top"> <b class="tl"></b> <b class="tr"></b> </b>
<div class="inner">
<div class="hd">
<h3>grab</h3>
</div>
<div class="bd">
<p>Body</p>
</div>
</div>
<b class="bottom"> <b class="bl"></b> <b class="br"></b> </b>
</div>

在这里.mod是父类,所有的类都是继承自它,.grab便是子类。
至于.top.innerbottom,顾名思义就是不同位置的子盒子。
这里是以“容器”为命名法则。

BEM

BEM 是块(Block)、 元素(Element)、修饰符( Modifier)的单词集合。
在选择器中,我们用以下三种符号来表示以上内容
•**-** 中划线 :仅作为连字符使用,表示某个块或者某个子元素的多单词之间的连接记号。•__ 双下划线:双下划线用来连接块和块的子元素•_ 单下划线:单下划线用来描述一个块或者块的子元素的一种状态
就像这样:type-block__element_modifier
官网的例子如下:
null

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

<style>
.button {
display:inline-block;
border-radius:3px;
padding:7px 12px;
border:1px solid #D5D5D5;
background-image:linear-gradient(#EEE,#DDD);
font:700 13px/18px Helvetica,arial;
}
.button--state-success {
color:#FFF;
background:#569E3D linear-gradient(#79D858,#569E3D) repeat-x;
border-color:#4A993E;
}
.button--state-danger {
color:#900;
}
</style>
<button class="button">Normal button</button>
<button class="button button--state-success">Success button</button>
<button class="button button--state-danger">Danger button</button>

SMACSS

SMACSS,一个长得很像OOCSS的规则。
核心只有以下6个:
•Base:页面的基本样式命名规则•Layout:布局命名规则•Module:模块规命名规则•State:状态命名规则•Theme:主题命名规则•Changing State:可变状态的命名规则
修饰符是--,子模块是__
官网的例子如下:
null

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<style>
#header {

}
#primarynav {

}
#maincontent {

}
</style>
<div id="header"></div>
<div id="primarynav"></div>
<div id="maincontent"></div>

为CSS赋能 —— 预处理器

CSS 预处理器是一个能让你通过预处理器自己独有的语法来生成CSS的程序。市面上有很多CSS预处理器可供选择,且绝大多数CSS预处理器会增加一些原生CSS不具备的特性,例如代码混合,嵌套选择器,继承选择器等。这些特性让CSS的结构更加具有可读性且易于维护。

sass

null
sass是诞生最早,也是世界上最成熟、最稳定、最强大的专业级CSS扩展语言!(官网说的(O_o)?? )
sass可用使用变量,嵌套规则,混合器,继承等编程语言才有的概念,代码例子如下:
$nav-color: #F90;
nav { $width: 100px; width: $width; color: $nav-color;}
//编译后
nav { width: 100px; color: #F90;}

less

xuehuayu.cn
Less 是一门 CSS 预处理语言,它扩展了 CSS 语言,增加了变量、Mixin、函数等特性,使 CSS 更易维护和扩展。
代码例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@base:#f938ab;
.box-shadow(@style,@c) when (iscolor(@c)) {
-webkit-box-shadow:@style @c;
box-shadow:@style @c;
}
.box-shadow(@style,@alpha:50%) when (isnumber(@alpha)) {
.box-shadow(@style,rgba(0,0,0,@alpha));
}
.box {
color:saturate(@base,5%);
border-color:lighten(@base,30%);
div {
.box-shadow(0 0 5px,30%)
}
}// 编译后.box {
color:#fe33ac;
border-color:#fdcdea;
}
.box div {
-webkit-box-shadow:0 0 5px rgba(0,0,0,0.3);
box-shadow:0 0 5px rgba(0,0,0,0.3);
}

stylus

null
Stylus,富于表现力、动态的、健壮的 CSS
代码例子如下:
body font 12px Helvetica, Arial, sans-serif
a.button border-radius 5px
完全不需要{} : ;的预处理器,个人是特别不喜欢这种写法,但是对于很多喜欢简洁的开发者来说,这确实非常好的编写方式

如魔法师一般的存在 —— CSS Houdini

有点时候眼看CSS出来新的属性,但是因为浏览器兼容的问题,所以往往是只能看而不能用,即便有的属性可以用,但也因为各浏览器的现实情况而存在意想不到的BUG,那么这就意味着一个属性出来之后我们要等到5年甚至更久之后才能使用吗?都9012年了耶?
当然不是,接下来我们可以了解一下这个如魔法师一般的存在 —— CSS Houdini

CSS Houdini是什么?

CSS Houdini是一组底层API,它们公开了CSS引擎的各个部分,从而使开发者可以通过这组API来扩展CSS。它让开发者拥有了直接访问CSSOM的能力,开发者可以通过这组API来编写浏览器可解析的CSS代码,这让开发者可以在不需要等待浏览器的实现的前提下实现自己想要的CSS功能。
null
[图片来自:https://www.qed42.com/blog/building-powerful-custom-properties-CSS-houdini]
如上所示,不同的API所对应的就是浏览器不同的渲染环节,用时下流行的概念来解释就是浏览器加载时不同生命周期的钩子函数。
简单来说,CSS Houdini就是JS IN CSS,niubility ..

CSS Houdini是怎么工作的?

我们可访问的7个API如下:
1.Typed OM API2.Properties & Values API3.Paint API4.Layout API5.Animation worklet6.Parser API7.Font Metrics API
Mmmm,虽然是有7个API(Houdini drafts上还有一些),但浏览器实际的支持情况其实是这样的:
xuehuayu.cn
[图片来自:https://ishoudinireadyyet.com/]
CSS Houdini的工作流程如下:
xuehuayu.cn
[图片来自:https://www.qed42.com/blog/building-powerful-custom-properties-CSS-houdini]
1.钩子进入渲染的进程中2.JS是这个钩子的核心3.使用JS的Typed OM,可以挂载自定义的属性,绘制图形,布局以及动画4.还有其他两个API:Parser API 和 Font Metrics API。它们用于注册CSS相关的新事物

一些示例

本篇不打算细讲CSS Houdini,所以不会画出所有的DEMO,有兴趣的可以查看底部的“资料来源”,从而获取更加详细的信息。
Typed OM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<style>
* {
margin:0;
padding:0;
}
.box {
background:linear-gradient(to right,#2c3e50,#4ca1af);
}
</style>
<div class="box" id="box"></div>
<script>
'use strict';
box.attributeStyleMap.set('width', CSS.px(200))
box.attributeStyleMap.set('height', CSS.px(200))
const [x, y] = 'width,height'.split(',').map(val => Number.parseInt(box.computedStyleMap().get(val)))
box.attributeStyleMap.set('transform', new CSSTranslate(CSS.px(x), CSS.px(y)))
console.log(box.computedStyleMap().get('transform'))
console.log(window.getComputedStyle(box, null)['transform'])
</script>

null
上面就是Typed OM的示例,这里值得一提的就是,如果我们用getComputedStyle去获取transform的值,最终结果是个矩阵,这其实不太方便我们做二次操作,但是用Typed OM的JS API computedStyleMap,去取的结果就是一个具体属性的集合,这是非常有利于我们进行二次操作的。
Paint API
Paint API就是允许你例如Canvas的属性来编写CSS样式,使用方法也很简单,我们可以看看https://slides.iamvdo.me/waq19/#/35上的示例
首先我们新建个文件叫registerPaint.js,在里面写下以下代码:

1
2
3
4
5
6
7
8
9
10
registerPaint('circle-ripple', class {
static get inputProperties() {
return ['--circle-color', '--circle-radius', '--circle-x', '--circle-y']
}
paint(ctx, geom, props, args) {
const x = props.get('--circle-x').value;
const y = props.get('--circle-y').value;
const radius = props.get('--circle-radius').value;
}
}

然后再新建一个index.html,并且在JS代码里注册上面写好的registerPaint.js,方式如下:CSS.paintWorklet.addModule('registerPaint.js');
具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<style>
.el {
--circle-radius:0;
--circle-color:deepskyblue;
background-image:paint(circle-ripple);
}
.el.animating {
transition:--circle-radius 1s,--circle-color 1s;
--circle-radius:300;
--circle-color:transparent;
}
</style>
<div class="el" id="el"></div>
<script>
'use strict'
CSS.paintWorklet.addModule('registerPaint.js');
el.addEventListener('click', e = >{
el.classList.add('animating');
el.attributeStyleMap.set('--circle-x', e.offsetX);
el.attributeStyleMap.set('--circle-y', e.offsetY);
});
</script>

所以我们有以下的效果:
null

CSS界的Babel —— PostCSS

说到底CSS Houdini其实也只是JS IN CSS,并不是纯正的CSS,那么对于一些新的CSS属性,我们相用的话,真的还得等5年后吗?还有即便是有各种工具,但是像一些兼容性写法,厂商前缀,循环,原生CSS也没有,我们不是还得需要依赖CSS预处理器吗?
其实也不是,这时候我们可以利用CSS界的Babel —— PostCSS

PostCSS是什么?

简单来说PostCSS就是可以让开发者使用JS来处理CSS的处理器,它分了以下5大类功能:

增强代码的可读性

利用从 Can I Use 网站获取的数据为 CSS 规则添加特定厂商的前缀。Autoprefixer[5] 自动获取浏览器的流行度和能够支持的属性,并根据这些数据帮你自动为 CSS 规则添加前缀。
例如我们输入以下代码:
:fullscreen {}
那么就会输出:
:-webkit-:full-screen {}:-moz-:full-screen {}:full-screen {}

将未来的 CSS 特性带到今天!

PostCSS Preset Env[6] 帮你将现代 CSS 语法转换成大多数浏览器都能理解的东西,根据你的目标浏览器或运行时环境来确定你需要的 polyfills,基于 cssdb 实现[7]。
例如我们输入以下代码:
@custom-media –med (width <= 50rem);
@media (–med) { a { &:hover { color: color-mod(black alpha(54%)); } } }
就会输出:
@media (max-width: 50rem) { a:hover { color: rgba(0, 0, 0, 0.54); } }

终结全局 CSS

CSS 模块[8] 就是说你永远不用担心命名太大众化而造成冲突太普通,只要用最有意义的名字就行了。
例如我们输入以下代码:
.name { color: gray;}
就会输出:
.Logo__name__SVK0g { color: gray;}

避免 CSS 代码中的错误

通过使用 stylelint 强化一致性约定并避免样式表中的错误[9],stylelint 是一个现代化 CSS 代码检查工具。它支持最新的 CSS 语法,包括类似 CSS 的语法,例如 SCSS 。
例如我们输入以下代码:
a { color: #d3;}
那么控制台会抛出错误:
app.css2:10 Invalid hex color

强大的网格系统

LostGrid[10] 利用 calc() 和你所定义的分割方式来创建网格系统,无需传递大量参数。
例如我们输入以下代码:
div { lost-column: 1/3 }
就会输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
div {
width:calc(99.9% * 1/3 - (30px - 30px * 1/3));
}
div:nth-child(1n) {
float:left;
margin-right:30px;
clear:none;
}
div:last-child {
margin-right:0;
}
div:nth-child(3n) {
margin-right:0;
float:right;
}
div:nth-child(3n + 1) {
clear:both;
}

可窥探的未来 —— cssdb

cssdb是postcss-preset-env的实现基准,主要就是CSS的新功能功能及这些功能从提出到成为标准时所在的进程。
cssdb跟ecma一样,对新属性分了不同的进程,具体的进程如下:
1.Stage 0:脑袋风暴阶段。高度不稳定,可能会发生变化。2.Stage 1:实验阶段。也非常不稳定,可能会发生变化,但是该提案已得到W3C成员的认可。3.Stage 2:承认阶段。高度不稳定并且可能会发生变化,但是正在积极研究中。4.Stage3:拥抱阶段。稳定且变化不大,此功能可能会成为标准。5.Stage4:标准阶段。最终的解决方案,所有主流浏览器都支持。
这就是postcss-preset-env依赖的实现基准,那么如果我们想要在我们的代码里使用这些Stage,该怎么做呢?
以我的脚手架ying-template为例,我们来查看在webpack中的实际配置:
首先我们先安装postcss以及其相应的插件:
npm install postcss postcss-loader postcss-preset-env postcss-nesting –save-dev
然后我们在webpack的config配置module中输入以下配置:

1
2
3
4
5
6
7
8
module: {
rules: [{
test: /\.css$/,
include,
exclude,
use: [ /* 你其它的loader */ 'postcss-loader']
}]
}

然后在根目录新建一个postcss.config.js

1
2
3
4
5
6
7
8
9
10
11
const postcssConfig = {
plugins: {
precss: {},
'postcss-preset-env': {
browsers: 'last 2 versions', // 浏览器兼容的版本
stage: 3 // 你用的属性所在的阶段
},
'postcss-nesting': {} // 这里就是你所使用的插件
}
};
module.exports = postcssConfig

这样就完成了,如果想看完整的配置,可以clone我的脚手架:https://github.com/KRISACHAN/ying-template
(这是个多页面的webpack4脚手架,可以使用ES6+语法,TypeScript,各类CSS处理器,ESLint以及单元测试等现代前端开发所需常用的东西,有兴趣的可以去看看。)
我们可以通过https://preset-env.cssdb.org/playground这个网站来查看具体的编译结果。
编译结果图如下:
null
是不是非常神奇呢?

后话

随着前端工程的普及,某E浏览器的没落,CSS的发展可谓是一日千里,近日也有一些数学属性的提案在发起,以后会发展成什么样,没人可以知道。只是总的来说,CSS的未来是一片光明的。本文简单分享了一些现代化的CSS知识,通过这些知识,我们很容易就能写出完备且现代化的CSS代码,能够给创造出更多的效益,希望大家可以积极地用起这些知识,并对CSS可以有更多的思考以及想象。

资料来源

1.https://developer.mozilla.org/zh-CN/docs/Archive/CSS32.http://oocss.org/3.http://getbem.com/4.http://smacss.com/5.https://sass-lang.com/6.http://lesscss.org/7.http://stylus-lang.com/8.https://blog.techbridge.cc/2017/05/23/css-houdini/9.https://www.smashingmagazine.com/2016/03/houdini-maybe-the-most-exciting-development-in-css-youve-never-heard-of/10.https://slides.iamvdo.me/waq19/fr/11.https://www.qed42.com/blog/building-powerful-custom-properties-CSS-houdini12.https://www.postcss.com.cn/13.https://cssdb.org/#staging-process14.https://s0dev0to.icopy.site/adrianbdesigns/postcss-preset-env-babel-for-css-12hp

References

[1] KRISACHAN: https://github.com/KRISACHAN
[2] Optional Chaining: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#optional-chaining
[3] ying-template: https://github.com/KRISACHAN/ying-template
[4] MDN: https://developer.mozilla.org/zh-CN/docs/Archive/CSS3
[5] Autoprefixer: https://github.com/postcss/autoprefixer
[6] PostCSS Preset Env: https://preset-env.cssdb.org/
[7] cssdb 实现: https://cssdb.org/
[8] CSS 模块: https://github.com/css-modules/css-modules
[9] stylelint 强化一致性约定并避免样式表中的错误: http://stylelint.io/
[10] LostGrid: https://github.com/peterramsing/lost

推荐阅读

(点击标题可跳转阅读)
你可能不知道的 CSS 计数器
10 个 CSS 高频面试题,你都会吗?
20 个让你效率更高的 CSS 代码技巧