CSS Index

CSS-SECRETS

# CSS3

CSS 1 的规范由 Håkon Wium Lie 和 Bert Bos 发表于 1996 年,它非常短,而且比较简单。它的内容少到用一个 HTML 页面就足以呈现了,即使用 A4 纸打印出来也只需要 68 页。

CSS 2 发表于 1998 年,它的定义更加严格,囊括了更多的功能,而且增加了两名编辑:Chris Lilley 和 Ian Jacobs。此时,规范的篇幅暴增到了480 页打印纸,人们已经无法把它完整地记忆下来了。

在 CSS 2 之后,CSS 工作组意识到这门语言已经变得非常庞大,再也无法把它塞进单个规范中了。这样不仅阅读和编辑极其困难,而且限制了 CSS本身的快速发展。别忘了,一项规范如果要推进到最终阶段,其中的每项特性都必须具备两个独立的实现和全面的测试。原先的那种方式已经玩不转了。因此,我们决定跨出一步,将 CSS 打散到多个不同的规范(模块)中,每个模块都可以独立更新版本。这其中,那些延续 CSS 2.1 已有特性的模块会升级到 3 这个版本号。比如以下模块:

此外,如果某个模块是前所未有的新概念,那它的版本号将从 1 开始。比如下面这些:

尽管“CSS3”这个名词非常流行,但它实际上并没有在任何规范中定义过。这一点跟 CSS 2.1 或更早的 CSS 1 不一样。真正的情况是,绝大多数编辑在提到这个词时,指的是一个非正式的集合,它包括 CSS 规范第三版(Level 3)再加上一些版本号还是 1 的新规范。尽管在哪些规范应该归入CSS3 的问题上,编辑们达成了一定的共识,但我们也不得不面对现实:由 于 CSS 的各个模块在近些年里以不同的速度在推进,我们已经越来越难以把这些规范以 CSS3、CSS4 这样的方式来划分了。

# CSS 编码技巧(尽量减少代码重复)

在软件开发中,保持代码的 DRY 和可维护性是最大的挑战之一,而这句话对 CSS 也是适用的。在实践中,代码可维护性的最大要素是尽量减少改动时要编辑的地方。灵活的 CSS 通常更容易扩展: 在写出基础样式之后,只用极少的代码就可以扩展出不同的变体,因为只需覆盖一些变量就可以了。 让我们来看一个例子,

先来看看下面这段 CSS,它给按钮添加了一些效果

padding: 6px 16px;
border: 1px solid #446d88;
background: #58a linear-gradient(#77a0bb, #58a);
border-radius: 4px;
box-shadow: 0 1px 5px gray;
color: white;
text-shadow: 0 -1px 1px #335166;
font-size: 20px;
line-height: 30px;
1
2
3
4
5
6
7
8

这段代码在可维护性方面存在一些问题, 当某些值相互依赖时,应该把它们的相互关系用代码表达出来。 在这个例子中,行高是字号的 1.5 倍。既然跨出了这一步,我们为什么还把字号定为绝对长度值呢?如果改用百分比或 em 单位就好多了,按照这种方法,我们就可以在一处控制按钮的所有尺寸样式了:(此时就需要重新审视到底哪些效果应该跟着按钮一起放大,而哪些效果是保持不变的。比如在这个例子中,我们希望按钮的边框粗细保持在 1px,不受按钮尺寸的影响。)

padding: .375em 1.25em;
border: 1px solid #446d88;
background: #58a linear-gradient(#77a0bb, #58a);
border-radius: .25em;
box-shadow: 0 .0625em .3125em gray;
color: white;
text-shadow: 0 -0.0625em .0625em #335166;
font-size: 1.25em;
line-height: 1.5;
1
2
3
4
5
6
7
8

不过,让按钮变大或变小并不是我们唯一想要改动的地方。颜色是另一个重要的变数。比如,假设我们要创建一个红色的取消按钮,或者一个绿色的确定按钮,该怎么做呢?眼下,我们可能需要覆盖四条声明(bordercolor、background、box-shadow 和 text-shadow),而且还有另一大难题:要根据按钮的亮面和暗面相对于主色调 #58a 变亮和变暗的程度来分别推导出其他颜色各自的亮色和暗色版本。此外,若我们想把按钮放在一个非白色的背景之上呢?显然使用灰色(gray)作投影只适用于纯白背景的情况。其实只要把半透明的黑色或白色叠加在主色调上,即可产生主色调的亮色和暗色变体,这样就能简单地化解这个难题了:

padding: .375em 1.25em;
border: 1px solid #446d88;
background: #58a inear-gradient(hsla(0,0%,100%,.2), transparent);
border-radius: .25em;
box-shadow: 0 .0625em .3125em rgba(0,0,0,.5);
color: white;
text-shadow: 0 -0.0625em .0625em rgba(0,0,0,.5);
font-size: 1.25em;
line-height: 1.5;
1
2
3
4
5
6
7
8

现在我们只要覆盖 background-color 属性,就可以得到不同颜色版本的按钮了

button.cancel { background-color: #c00; }
button.ok { background-color: #6b0; }
1

我们的按钮现在已经非常灵活了。不过,这个例子并没有涵盖所有能让代码变得更 DRY 的方法。

# 1. 有时候,代码易维护和代码量少不可兼得

# 2. currentColor

在 CSS 颜色(第三版)规范中,增加了很多新的颜色关键字,比如 lightgoldenrodyellow 等,其实并不是很常用。但是,我们还得到了一个特殊的颜色关键字 currentColor,它是从SVG 那里借鉴来的。这个关键字并没有绑定到一个固定的颜色值,而是一直被解析为 color的值 。实际上,这个特性让它成为了 CSS 中有史以来的第一个变量。举个例子,假设我们想让所有的水平分割线(所有 <hr> 元素)自动与文本的颜色保持一致。有了 currentColor 之后,我们只需要这样写:

hr {
    height: .5em;
    background: currentColor; 
}
1
2
3
4

其实 currentColor 本身就是很多 CSS 颜色属性的初始值,比如border-color 和 outline-color,以及 text-shadow 和 box-shadow 的颜色值,等等。

# 3. 继承

关键字inherit 可以用在任何 CSS 属性中,而且它总是绑定到父元素的计算值(对伪元素来说,则会取生成该伪元素的宿主元素)。举例来说,要把表单元素的字体设定为与页面的其他部分相同,你并不需要重复指定字体属性,只需利用 inherit 的特性即可:

input, select, button { font: inherit; }
1

与此类似,要把超链接的颜色设定为与页面中其他文本相同,还是要用inherit:

a { color: inherit; }
1

这个 inherit 关键字对于背景色同样非常有用。举个例子,在创建提示框的时候,你可能希望它的小箭头能够自动继承背景和边框的样式:

.callout { position: relative; }
.callout::before {
 content: "";
 position: absolute;
 top: -.4em; left: 1em;
 padding: .35em;
 background: inherit;
 border: inherit;
 border-right: 0;
 border-bottom: 0;
 transform: rotate(45deg);
}
1
2
3
4
5
6
7
8
9
10
11
12

# 关于视错觉

人的眼睛并不是一台完美的输入设备。有时候精准的尺度看起来并不精准,而我们的设计需要顺应这种偏差。举一个在视觉设计领域广为人知的例子吧,我们的眼睛在看到一个完美垂直居中的物体时,会感觉它并不居中。实际上,我们把这个物体从几何学的中心点再稍微向上挪一点,才能取得理想的视觉效果。

在第一个矩形中,棕色方块在数学层面上是完美垂直居中的,但看起来并不是这样;在第二个矩形中,方块从几何中心向上轻微移动了一点儿

与此类似,在字体设计领域广为人知的是,圆形的字形(比如 0)与矩形字形相比,需要稍微放大一些,因为我们倾向于把圆形感知得比其实际尺寸更小一些。

这些视觉上的错觉在任何形式的视觉设计中都普遍存在,需要我们有针对性地进行调整。一个非常常见的例子是给一个文本容器设置内边距。不论内容文本有多长,是一个单词还是几个段落,这个问题都会出现。假如我们给容器的四边指定相同的内边距,则实际效果看起来并不相等。原因在于,字母的形状在两端都比较整齐,而顶部和底部则往往参差不齐,从而导致你的眼睛把这些参差不齐的空缺部分感知为多出来的内边距。因此,如果我们希望四边的内边距看起来是基本一致的,就需要减少顶部和底部的内边距。

# 关于响应式网页设计

响应式网页设计(Responsive Web Design,RWD)比较常见的实践是用多种分辨率来测试一个网站,然后添加越来越多的媒体查询(Media Query)规则来修补网站在这些分辨率下出现的问题。然而对于今后的 CSS 改动来说,每个媒体查询都会增加成本。 只要用对了,它就是利器。但是,你只应该把它作为最后的手段。

媒体查询的断点不应该由具体的设备来决定,而应该根据设计自身来决定。这不仅是因为我们的网站需要面向的设备太多了(尤其是考虑到未来的设备时),还因为一个网站在桌面端可能会以任意尺寸的窗口来显示。

遵从“尽量减少代码重复”所描述的原则对此也是有帮助的,因为你不需要去覆盖媒体查询里同样数量的声明。这在本质上减轻了它们所产生的维护成本。

下面还有一些建议,可能会帮你避免不必要的媒体查询。

  • 使用百分比长度来取代固定长度。如果实在做不到这一点,也应该尝试使用与视口相关的单位(vw、vh、vmin 和 vmax),它们的值解析为视口宽度或高度的百分比。
  • 当你需要在较大分辨率下得到固定宽度时,使用 max-width 而不是width,因为它可以适应较小的分辨率,而无需使用媒体查询。
  • 不要忘记为替换元素(比如 img、object、video、iframe 等)设置一个 max-width,值为 100%。
  • 假如背景图片需要完整地铺满一个容器,不管容器的尺寸如何变化,background-size: cover 这个属性都可以做到。但是,我们也要时刻牢记——带宽并不是无限的,因此在移动网页中通过 CSS 把一张大图缩小显示往往是不太明智的。
  • 当图片(或其他元素)以行列式进行布局时,让视口的宽度来决定列的数量。弹性盒布局(即 Flexbox)或者 display: inline-block加上常规的文本折行行为,都可以实现这一点。
  • 在 使 用 多 列 文 本 时, 指 定 column-width( 列 宽 ) 而 不 是 指 定column-count(列数),这样它就可以在较小的屏幕上自动显示为单列布局。

总的来说,我们的思路是尽最大努力实现弹性可伸缩的布局,并在媒体查询的各个断点区间内指定相应的尺寸。当网页本身的设计足够灵活时,让它变成响应式应该只需要用到一些简短的媒体查询代码。

# 合理使用简写

合理使用简写是一种良好的防卫性编码方式,可以抵御未来的风险。

展开式属性与简写属性的配合使用也是非常有用的,可以让代码更加DRY。对于那些接受一个用逗号分隔的列表的属性(比如 background),尤其如此。

background: url(tr.png) no-repeat top right / 2em 2em,
 url(br.png) no-repeat bottom right / 2em 2em,
 url(bl.png) no-repeat bottom left / 2em 2em;
1
2
3

background-size 和 background-repeat 的值被重复了三遍,尽管每层背景的这两个值确实是相同的。其实我们可以从 CSS 的“列表扩散规则”那里得到好处。它的意思是说,如果只为某个属性提供一个值,那 它就会扩散并应用到列表中的每一项。因此,我们可以把这些重复的值从简写属性中抽出来写成一个展开式属性:

background: url(tr.png) top right,
 url(br.png) bottom right,
 url(bl.png) bottom left;
background-size: 2em 2em;
background-repeat: no-repeat;
1
2
3
4
5