现代 CSS 进化史

2021-02-11 02:34

阅读:646

YPE html>

技术图片

简评:CSS 是一门入门比较简单,但真正使用起来又很困难的语言(有些人认为它不应该称为一门语言)。CSS 看起来凌乱复杂,其实攻克也不难,了解了 CSS 的发展背景你就会有对它有了系统性的认知。通过这一篇文章,你基本就会对 CSS 有了一个熟识的了解。

技术图片

CSS 一直被web 开发者认为是最简单也是最难的一门奇葩语言。它的入门确实非常简单——你只需为元素定义好样式属性和值,看起来似乎需要做的工作也就这样嘛!然而在一些大型工程中 CSS 的组织是一件复杂和凌乱的事情,你更改页面上任意一个元素的一行 CSS 样式都有可能影响到其他页面上的元素。

为了解决 CSS 错综复杂的继承问题,开发者建立了各种不同的最佳实践,问题是哪一个最佳实践是最好的目前尚无定论,而且有些实践相互之间是完全矛盾的。如果你第一次尝试学习 CSS ,这对于你来说是相当迷惑的。

这篇文章的目的是通过回顾 CSS 的历史背景,介绍下时至2018年的今天 CSS 发展过程中的一些设计模式和工具的演变。通过对这些背景的理解,你将会更轻松的理解每个设计思想并且学以致用。接下来让我们开始吧!

CSS 基本样式使用

我们从一个最简单的网页 index.html 开始,这个文件中包含一个独立的样式文件 index.css :



Modern CSS

This is the header.

This is the main content.

...

This is an aside section.

...

This is the footer.

上面的 HTML 标签中没用使用任何 class 或者 id。
在没有任何 CSS 样式的情况下,我们的网站看起来是这个样子:

技术图片
点击查看在线demo

功能可用,但看起来不好看,我们可以继续在 index.css 加点 CSS 美化下排版:

/* BASIC TYPOGRAPHY                       */
/* from https://github.com/oxalorg/sakura */
html {
  font-size: 62.5%;
  font-family: serif;
}
body {
  font-size: 1.8rem;
  line-height: 1.618;
  max-width: 38em;
  margin: auto;
  color: #4a4a4a;
  background-color: #f9f9f9;
  padding: 13px;
}
@media (max-width: 684px) {
  body {
    font-size: 1.53rem;
  }
}
@media (max-width: 382px) {
  body {
    font-size: 1.35rem;
  }
}
h1, h2, h3, h4, h5, h6 {
  line-height: 1.1;
  font-family: Verdana, Geneva, sans-serif;
  font-weight: 700;
  overflow-wrap: break-word;
  word-wrap: break-word;
  -ms-word-break: break-all;
  word-break: break-word;
  -ms-hyphens: auto;
  -moz-hyphens: auto;
  -webkit-hyphens: auto;
  hyphens: auto;
}
h1 {
  font-size: 2.35em;
}
h2 {
  font-size: 2em;
}
h3 {
  font-size: 1.75em;
}
h4 {
  font-size: 1.5em;
}
h5 {
  font-size: 1.25em;
}
h6 {
  font-size: 1em;
}

这地方大部分都是关于排版(字体、行高等)样式的定义,也包含一些颜色和一个 layout 居中设置。为了让每个属性有个合理的值你需要学习点设计理论,但是这个地方我们用到的 CSS 本身并不复杂,你可以直接定义,结果如下所示:

技术图片

Click here to see a live example

有所变化了吧!正如 CSS 许诺的一样——用一种简单的方式给文档添加上样式,不需要编程或者复杂的业务逻辑。不幸的是,实际情况会复杂的很多,我们不单单使用的是 CSS 的排版和颜色这种简单的样式定义。

CSS 的布局使用

在20世纪90年代,CSS 还未广泛普及之前,对于页面的布局没有太多的选择。HTML 最初是被设计为创建纯文本的一门语言,并不是包含侧边栏、列等布局的动态页面。早期的时候,页面布局通常使用的是 HTML 表格,在行和列中组织内容,这种方式虽然有效,但是把内容和表现杂糅在一块了,如果你想改变网页的布局就得需要修改大量的 HTML 代码。

CSS 的出现推动了内容(HTML)和表现(CSS)的分离,人们开始把所有的布局代码从 HTML 中移除放入到 CSS 中,需要注意的是,和 HTML 一样 CSS 的设计也不是用来做网页内容布局的,所以早期的时候试图解决这种分离设计是很困难的。

我们来用个实际例子来看下如何实现布局,在我们定义 CSS 布局前先重置下 padding 和 margin(会影响布局的计算),不同的区域我们定义不同的颜色(不要太在意好看不好看只要不同区域间足够醒目就可以)

/* RESET LAYOUT AND ADD COLORS */
body {
  margin: 0;
  padding: 0;
  max-width: inherit;
  background: #fff;
  color: #4a4a4a;
}
header, footer {
  font-size: large;
  text-align: center;
  padding: 0.3em 0;
  background-color: #4a4a4a;
  color: #f9f9f9;
}
nav {
  background: #eee;
}
main {
  background: #f9f9f9;
}
aside {
  background: #eee;
}

现在页面应该看起来如下:

技术图片

Click here to see a live example

接下来我们用 CSS 来布局下页面内容,我们将按照时间顺序采用三种不同的方式,先从最经典的浮动布局开始吧。

基于浮动的布局

CSS 浮动属性最初是为了将图片浮动在一列文本的左侧或者右侧(报纸上经常看到)。早在21世纪初,web 开发者将这个属性的优势扩展到了任意的元素,这意味着你可以通过 div 的内容浮动创建出行和列的错觉。同样,浮动也不是基于这样的目的设计的,所以兼容性上会有很多问题。

2006年,A List Apart 上发表了一篇热门文章 In Search of the Holy Grail,文章概述了实现圣杯布局的详细方法——一个头部、三列内容和一个底部,你一定觉得一个简单的布局被称为圣杯布局很疯狂吧,但是在当时纯 CSS 的时代这的确很难实现。

下面是一个基于浮动布局的例子,用到了我们文章中提到的一些技术点:

/* FLOAT-BASED LAYOUT */
body {
  padding-left: 200px;
  padding-right: 190px;
  min-width: 240px;
}
header, footer {
  margin-left: -200px;
  margin-right: -190px;   
}
main, nav, aside {
  position: relative;
  float: left;
}
main {
  padding: 0 20px;
  width: 100%;
}
nav {
  width: 180px;
  padding: 0 10px;
  right: 240px;
  margin-left: -100%;
}
aside {
  width: 130px;
  padding: 0 10px;
  margin-right: -100%;
}
footer {
  clear: both;
}
* html nav {
  left: 150px;
}

仔细看下 CSS 代码,这里面为了让它工作包含一些必须的 hack 方式(负边距、clear: both、硬编码的宽度计算等),稍后我们会对这些细节做详细的解释。最终的结果如下:

技术图片

Click here to see a live example

看起来不错了,但是通过三列的颜色可以看出来他们的高度不一样,页面的高度也没有填充满屏幕。这些问题是浮动布局导致的,所有的浮动只是将内容放在某一区块的左边或者右边,但是没法知道其他区块的高度。这个问题一直没有个好的解决方案,直到 Flexbox 布局的出现。

基于 Flexbox 的布局

Flexbox CSS 属性实在2009年第一次提出来的,但直到2015年才得到浏览器的广泛支持。Flexbox 被设计为定义一个空间在行或者列上如何分布的,这让它比浮动更适合用来做布局,这意味在使用浮动布局十多年后,web开发者终于不再使用带有hack的浮动布局方式了。

下面是一个基于 Flexbox 布局的例子。注意为了让 Flexbox 生效我们需要在三列的外面额外包装一个 div:





Modern CSS

This is the header.

This is the main content.

...

This is an aside section.

...

This is the footer.

下面是 Flexbox 布局的 CSS 代码:

/* FLEXBOX-BASED LAYOUT */
body {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
.container {
  display: flex;
  flex: 1;
}
main {
  flex: 1;
  padding: 0 20px;
}
nav {
  flex: 0 0 180px;
  padding: 0 10px;
  order: -1;
}
aside {
  flex: 0 0 130px;
  padding: 0 10px;
}

这种方式和浮动布局相比更加紧凑了,虽然 flexbox 一些属性和值初看起来有些困惑,但是好歹不需要像浮动布局那样负边距的 hack 方案了,这是个巨大的进步。最终结果如下:

技术图片

Click here for a live example

效果好多了!所有的列高度都相同,并且占据了整个页面的高度。从某种意义上来说这似乎是完美的了,但是这个方式也有些小问题,其中一个就是浏览器的兼容性——主流的现代浏览器都支持flexbox,但是一些旧的浏览器不兼容。幸运的是浏览器厂商也正在尽最大努力终止对旧版本浏览器的支持,为web开发者提供更一致的开发体验。另一个问题是我们需要

包裹HTML内容标签,如果能避免会更完美。理想状态下,任何CSS布局都不需要改变HTML标签的。

最大的缺点是CSS代码本身——flexbox虽然去掉了浮动的Hack,但是代码的可读性上变得更差了。你很难去理解flexbox的CSS,并且不知道页面上是如何去布局所有元素的。在写flexbox布局代码的时,有很多时候靠的是大量的猜测和尝试。

特别需要注意的是,flexbox被设计用来在单行或者单列中分割元素的——它不是设计用来给整个页面做布局的!尽管它能很好的实现(相对于浮动布局好很多)。另一种不同的规范是用来处理多行或者多列布局的,我们称之为CSS 网格。

基于 Grid的布局

CSS网格最早在2011年提出的(比flexbox提案晚不了多久),但是花了好长时间才在浏览器上普及起来。截止2018年初,大多数现代浏览器都已经支持CSS grid(这比一两年前有巨大的进步了)
下面我们看一下基于网格布局的例子,注意在这个例子中我们摆脱了flexbox布局中必须使用

的限制,我们可以简单的使用原始的HTML,先看下CSS文件:
/* GRID-BASED LAYOUT */
body {
  display: grid;
  min-height: 100vh;
  grid-template-columns: 200px 1fr 150px;
  grid-template-rows: min-content 1fr min-content;
}
header {
  grid-row: 1;
  grid-column: 1 / 4;
}
nav {
  grid-row: 2;
  grid-column: 1 / 2;
  padding: 0 10px;
}
main {
  grid-row: 2;
  grid-column: 2 / 3;
  padding: 0 20px;
}
aside {
  grid-row: 2;
  grid-column: 3 / 4;
  padding: 0 10px;
}
footer {
  grid-row: 3;
  grid-column: 1 / 4;
}

虽然结果看起来和基于 flexbox 的布局一样,但是 CSS 在很大程度上得到了改进,它清晰地表达出了期望的布局方式。行和列的大小和形状在 body 选择器中定义,每一项 item 直接通过他们所在行和列的位置定义。

grid-column 这个属性你可能觉得不太好理解,它定义了列的起点和终点。这个地方让你觉得困惑的可能是明明有 3 列,却为什么定义的范围是1 到 4,通过下面的图片你就能理解了:

技术图片

Click here to see a live example

第一列是从1 到 2,第二列是从2 到 3,第三列从3 到 4,所以头部的grid-column是从1 到4 占据整个页面,导航的grid-column是从1 到2 占据第一列等等

一旦你习惯了 grid 语法,你会觉得它是一种非常理想的 CSS 布局方式。唯一缺点就是浏览器支持,幸运的是过去一年中浏览器的支持又得到了进一步的提高。作为专为 CSS 设计的第一款真正的布局工具很难描绘它的重要性,从某种意义上来说,由于现有的工具需要太多的hack和变通方式去实现,因此web设计者过去对于布局的创意上一直很保守,CSS 网格的出现有可能会激发出一批从未有过的创意布局设计——想想还是挺激动人心的!

技术图片

使用 CSS 预处理器扩展 CSS 语法

到目前为止,我们介绍了 CSS 的基本样式和布局,现在我们再来看下那些帮助 CSS 提升语言本身体验的工具,先从 CSS 预处理器开始吧。

CSS 预处理器允许你使用不同的语言来定义样式,最终会帮你转换为浏览器可以解释的 CSS,这一点在当今浏览器对新特性支持缓慢的情况下很有价值。第一个主流的 CSS 预处理器是2006年发布的 Sass,它提供了一个新的更简洁的语法(缩进代替大括号,没有分号等等),同时增加了一些 CSS 缺失的高级特性,像变量、工具方法还有计算。下面我们使用 Sass 变量实现下前面例子中带颜色的区域定义:

$dark-color: #4a4a4a
$light-color: #f9f9f9
$side-color: #eee
body
  color: $dark-color

header, footer
  background-color: $dark-color
  color: $light-color

main
  background: $light-color
nav, aside
  background: $side-color

注意我们用$定义了可复用的变量,省略了大括号和分号,语法看起来更加清晰了。简洁的语法让 Sass 看起来很棒,但变量这样的特性出现在当时来说意义更大,这为编写整洁可维护的CSS 代码开辟了新的可能性。
使用Sass你需要安装Ruby(Ruby),这门语言主要是让 Sass 编译成正常的 CSS,同时你需要安装Sass gem,之后你就可以通过命令行把你的 .sass 文件转成 .css 文件了,我们先看一个使用命令行的例子:

sass --watch index.sass index.css

这个命令定期把index.sass中的 Sass 代码转为 CSS 写入到index.css文件中(--watch参数设定后会实时监听 .sass 文件改动并执行编译,非常方便)

这个过程被称为构建步骤。这在2006年的时候是非常大的一个障碍,如果你对 Ruby 这样的编程语言熟悉的话,这个过程非常简单。但是当时很多前端开发者只用 HTML 和 CSS,他们不需要类似这样的工具。因此,为了使用 CSS 预编译的功能而让一个人学习整个生态系统是很大的一个要求了。

2009年的时候,Less CSS 预编译器发布。它也是 Ruby 写的,并且提供了类似于 Sass 的功能,关键不同点是它的语法设计上更接近 CSS。这意味着任何 CSS 代码都是合法的 Less 代码,同样我们看一个用 Less 语法的例子:

@dark-color: #4a4a4a;
@light-color: #f9f9f9;
@side-color: #eee;
body {
  color: @dark-color;
}

header, footer {
  background-color: @dark-color;
  color: @light-color;
}

main {
  background: @light-color;
}
nav, aside {
  background: @side-color;
}

语法上几乎是相同的(变量的定义使用@替代了$),但是 Less 和 CSS 一样带有大括号和分号,没有 Sass 例子的代码看起来漂亮。然而,和 CSS 相近的特性反而让开发者更容易接受它,在2012年,Less 使用了JavaScript(Node.js)重写了替换了 Ruby,性能上比 Ruby 编译更快了,并且很多在工作中使用了 Node.js 的人更容易上手了。

把这段代码转化为标准的 CSS,你需要安装 Node.js 和 Less,执行的命令行如下:

lessc index.less index.css

这个命令把index.less文件中的Lessz代码转化为标准的 CSS 代码写入到index.css文件中,注意 lessc 命令不能监听文件的变化(和 sass 不一样),这意味着你需要安装其他自动监听和编译的组件来实现该功能,增加了流程的复杂性。同样,对于程序员来说使用命令行的方式并不难,但是对于其他只想使用 CSS 预编译器的人来说还是个非常大的障碍。

汲取了 Less 的经验,Sass 开发者在2010年发布了一个新的语法叫 SCSS(与 Less 类似的一个 CSS 超集),同时发布了 LibSass,一个基于 C++ 扩展的 Ruby 引擎,让编译更快并且适配于多种语言。
另外一个 CSS 预处理器是2010年发布的 Stylus,使用 Node.js 编写,和 Sass 或者 Less 相比更注重于清晰的语法。通常主流的CSS预编译器就这三种(Sass,Less,Stylus),他们在功能方面非常相似,所以你不必担心选择哪一个会是错误的。

然而,有些人认为使用CSS预处理器开始变得越来越没必要,因为浏览器最终会慢慢实现这些功能(像变量和计算)。此外,还有一种称为 CSS 后处理器的方法,有可能会让 CSS 预处理器过时(显然这存在些争议),我们在后面会详细介绍下。

使用 CSS 后处理器的转换功能

CSS 后处理器使用 JavaScript 分析并转换你的 CSS 为合法 CSS,从这方面来看和 CSS 预处理器很相似,你可以认为是解决同一个问题的不同方式。关键的不同点是 CSS 预处理器使用特殊的语法来标记需要转换的地方,而 CSS 后处理器可以解析转换标准的 CSS,并不需要任何特殊的语法。举一个例子来说明下,我们用最初定义的 header 标签样式来看一下吧:

h1, h2, h3, h4, h5, h6 {
  **-ms-hyphens: auto;
  -moz-hyphens: auto;
  -webkit-hyphens: auto;**
  hyphens: auto;
}

粗体部分的属性成为厂商前缀,厂商前缀是浏览器厂商对 CSS 新功能的实验和测试使用的,在正式实现前提供给开发者使用 CSS 新属性的一种方式。-ms代表IE浏览器,-moz是火狐浏览器,-webkit是基于 webkit 内核的浏览器。

定义这些不同浏览器厂商的前缀属性是非常烦人的,尽量使用生成工具自动添加厂商前缀。我们可以使用 CSS 预处理器来完成这个功能,例如,我们可以用 SCSS 来实现:

@mixin hyphens($value) {
  -ms-hyphens: $value;
  -moz-hyphens: $value;
  -webkit-hyphens: $value;
  hyphens: $value;
}
h1, h2, h3, h4, h5, h6 {
  @include hyphens(auto);
}

这个地方使用了 Sass 的 mixin 功能,你可以定义一个 CSS 代码块然后在其他任何地方重用,当这个文件被编译成标准的 CSS 的时候,所有的@include语句都被替换成与之匹配的@mixin中的 CSS。总体来说,这个解决方案也不差,但是你仍然要为每个需要厂商前缀的 CSS 属性定义一个 mixin,这些 mixin 的定义将需要不断的维护,比如当浏览器支持了某个 CSS 属性后你就要在你的定义中移除掉该属性。

比起写 mixin 的方式,直接正常写 CSS 然后由工具自动识别添加需要厂商前缀的属性的方式显然更优雅些。CSS 后处理器就恰好能完成这样的功能。比如,如果你使用 PostCSS 和 autoprefixer 插件,你就可以直接写正常的CSS并不需要指定浏览器厂商前缀,剩下的工作全交给后置处理器去处理:

h1, h2, h3, h4, h5, h6 {
  hyphens: auto;
}

当你使用CSS后处理器运行这段代码的时候hyphens: auto; 将被替换成包含所有浏览器厂商前缀的属性,这意味着你可以正常写CSS不用担心各种浏览器兼容性问题,岂不是很棒!
除了PostCSS的autoprefixer插件还有很多有意思的插件,cssnext 插件可以让你体验下一些实验性质的CSS新功能,CSS modules 可以自动改变 class 的名字避免名称冲突,stylelint 能检查出


评论


亲,登录后才可以留言!