💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
> 原文出处:http://www.w3cplus.com/css3/css-secrets/css-coding-tips.html ## 缩简代码 在软件开发过程中,保持代码的简洁和可维护性是最大的挑战,对于 CSS 来说,同样如此。实际上,可维护代码的一个重要特性就是要**缩简需求变化时所需修改的代码量**。假设放大一个按钮需要对十处代码做出修改,那么你就有可能遗漏其中的一些细节,如果这些代码本来就不是你写的,那么就更有可能发生这种疏漏。即使需要修改的细节显而易见,即使你准确地找到了这些细节,那么你也在无形中浪费了大量的时间——这些时间本该有更大的创造力。 此外,这并不只是应对未来的需求变化。可扩展的 CSS 在完成首次编写后,只需要用少量代码创建适当的变量,那么进行重写和覆盖时所需的代码量就会很少。下面让我们来看一个例子。 请先看一下下面的 CSS,它被用来美化下图所示的按钮: ![按钮](https://box.kancloud.cn/2015-10-01_560cd9cc1e96c.png "按钮") ~~~ 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; ~~~ 这段代码在可维护性上有几点问题,让我们来修改一下。首先看到的就是字体的单位。如果我们想要更改字体大小(比如为了创建更大、更显眼的按钮),那么就需要同时修改行间距,因为它们在这里使用的都是绝对值。此外,这里的行间距并不能有效地与字体大小关联起来,以至于我们需要手动计算各种字体大小下的行间距。**当属性值相互关联时,应该在代码中体现它们的关联性。**对于上述的代码,行间距是行高的 `150%`,所以,使用下面的代码将更具可维护性: ~~~ font-size: 20px; line-height: 1.5; ~~~ 既然我们都写成了这样,为什么还要给字体大小指定一个绝对数值?虽然绝对数值易于使用,但是每次需求变化时你就需要重新修改,比如现在我们想让父级字体更大一些,那么就需要在样式表中使用绝对数值修改每一条相关的样式规则,显然这是不可取的。更好的方式是使用百分比或者类似 **`em`** 的单位: ~~~ font-size: 125%; /* 假设父级字体为 16px */ line-height: 1.5; ~~~ 现在,如果我修改了父级字体大小,那么按钮也会相应的变大。不过,按钮看起来和之前不大一样了,如下图所示: ![按钮](https://box.kancloud.cn/2015-10-01_560cd9cdbd08d.png "按钮") 这是因为其他的特效还是和之前一样袖珍,仍然不具有伸缩性。只需要使用类似 **`em`** 的单位,我们就可以将其他特效变成可伸缩的,最终所有的属性值都关联到了字体大小上。至此,我们只需修改字体大小就能控制整个按钮的大小了。 ~~~ padding: .3em .8em; border: 1px solid #446d88; background: #58a linear-gradient(#77a0bb, #58a); border-radius: .2em; box-shadow: 0 .05em .25em gray; color: white; text-shadow: 0 -.05em .05em #335166; font-size: 125%; line-height: 1.5; ~~~ 现在,按钮看起来很像是原始版本的放大版: ![按钮](https://box.kancloud.cn/2015-10-01_560cd9d390e05.png "按钮") *在这里,我们想要字体大小与父级字体相关联,所以使用了 **`em`**。在某些情况下,你想要让字体和根节点的字体相关联(比如`<html>` 节点的字体),但是这时使用 **`em`** 就会让计算变得非常复杂。此时,最好使用 `rem` 单位。虽然“相关性”在 CSS 中很重要,但你关联前需要考虑下什么元素需要被关联在一起。 * 请注意,在某些属性上我们仍然使用了绝对数值。**对于按钮的哪些效果需要可伸缩,哪些不需要可伸缩这一问题,应该主观判断而不能客观要求。**比如此处的按钮,不论按钮如果缩放,我们始终要求它的边框粗细为 `1px`。 不过,通常我们所需要修改的不只是按钮的尺寸。对配色的修改也是一个很重要的方面。比如,如果我们想要创建一个红色的“取消”按钮,或者是绿色的 “OK” 按钮又该如何做呢?一般来说,我们至少需要重写四条样式(**`border-color`**,**`background`**,**`box-shadow`**,**`text-shadow`**)。不用多说你也会理解,重新计算主体颜色(**`#58a`**)的亮度、理清所需颜色的亮度,将是一份非常麻烦的事情。此外,如果我们将按钮添加到了非白色背景的页面中,又该如何修改呢?实际上,上述按钮的灰色阴影只在白色背景下才会效果明显。 一个简单的修改方式是,对主体颜色的亮度叠加半透明的黑白色: ~~~ padding: .3em .8em; border: 1px solid rgba(0,0,0,.1); background: #58a linear-gradient(hsla(0,0%,100%,.2),transparent); border-radius: .2em; box-shadow: 0 .05em .25em rgba(0,0,0,.5); color: white; text-shadow: 0 -.05em .05em rgba(0,0,0,.5); font-size: 125%; line-height: 1.5; ~~~ > 提示:使用 `HSLA` 而不是 `RGBA` 表示半透明白色的好处在于,由于无需重复,它的字符更少,编写的更快。 接下来,我们可以使用不同的颜色重写背景颜色了,如下图所示: ![按钮](https://box.kancloud.cn/2015-10-01_560cd9e7e907a.png "按钮") ~~~ button.cancel { background-color: #c00; } button.ok { background-color: #6b0; } ~~~ 现在,整个按钮的样式更加灵活了。不过,这个示例并没有阐述怎样让代码保持简洁,更多技巧请阅读接下来的内容。 ### 可维护性 VS 简洁 有些时候,**代码的可维护性和简洁是相互敌对的。**就像是前面的按钮示例,最终的代码量比最初的代码量还增加了一些。请思考一下下面的这段代码,它的作用是为某元素设置边框粗细,除左边框外都设置为 `10px`: ~~~ border-width: 10px 10px 10px 0; ~~~ 虽然只是一条样式,但是当我们修改边框的粗细时,至少要修改三处地方。如果我们将该样式写成两句,那么修改起来就会更加方便,而且可以预见的是也更加冗余阅读: ~~~ border-width: 10px; border-left-width: 0; ~~~ > 有些人可能会争论说 `em` 单位才是 CSS 的第一个变量,因为它引用了 **`font-size`** 的值。大多数的百分比也是同样的角色,虽然这并不令人感到兴奋。 ### currentColor **[CSS Color Level 3](http://www.w3.org/TR/css3-color/)** 为开发者提供了许多新的颜色关键字,比如 **lightgoldenrodyellow**——虽然目前来看这并没多大用处。不过,其中也有一些非常有用的颜色关键字,比如从 SVG 借鉴来的 **`currentColor`**。实际上,该属性并不是一个固定不变的颜色值,它会根据 **`color`** 的属性值生成相应的属性值——这让它成为了 CSS 中的第一个变量。虽然它的作用有限,但意义重大。 > 有关于`currentColor`相关的介绍,可以阅读《[使用CSS的currentColor变量扩展颜色级联](http://www.w3cplus.com/css3/extending-the-color-cascade-with-the-css-currentcolor-variable.html)》一文。 让我们来举个例子,假设要让所有的水平线(`<hr>`)和文本保持统一的颜色,那么就可以使用 **currentColor**: ~~~ hr { height: .5em; background: currentColor; } ~~~ 你可能已经注意到了,现有的很多属性就有相同的特性。比如,如果没有给边框添加颜色,那么边框的颜色会被渲染为文本的颜色。这是因为 **`currentColor`** 同样是许多 CSS 颜色属性的初始值:**`border-color`**、**`box-shadow`**、**`outline-color`**…… 未来,当我们可以 CSS 的原生属性控制颜色时,那时我们能够使用更多的变量,而 **`currentColor`** 也将会更有用处。 ### 继承 虽然很多开发者意识到了 **`inherit`** 关键字的重要性,但往往会忘记使用它。**`inherit`** 可以被应用到任何的 CSS 属性上,并且会根据父级元素的属性计算出恰当的值(如果是伪元素,那么就会根据当前元素属性来计算)。比如,让表单元素和页面其他元素具有相同的字体,无需一一指定,直接使用 **`inherit`** 即可: ~~~ input, select, button { font: inherit; } ~~~ 同样的道理,让超链接和页面文本具有相同的颜色,也可以使用 **`inherit`**: ~~~ a { color: inherit; } ~~~ **`inherit`** 关键字也同样适用于 **`background`** 之类的属性。比如,创建一个拼写气泡(speech bubbles),让它自动继承输入框的背景和边框,如下图所示: ![气泡](https://box.kancloud.cn/2015-10-01_560cd9fc45897.png "气泡") *一个拼写检查气泡,它获得了父级元素的背景色和边框。* ~~~ .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); } ~~~ ## 相信你的眼睛,而不是数字 人类的双眼并不是完美的输入设备(input device)。有时候,精确的尺寸却在视觉上呈现了一种不精确的感觉。比如在视觉设计中,众所周知的是当元素垂直居中时,有些细节是无法察觉的。相反,元素需要略高于几何中心,才能被视为垂直居中。如下图所示: ![气泡](https://box.kancloud.cn/2015-10-01_560cd9fcb3d62.png "气泡") *在第一个矩形中,棕色的正方形经数学计算是垂直居中的,但视觉上并不是;在第二个矩形中,棕色的正方形被放置在了略高于中心的位置,但视觉上更像是垂直居中。* 同样在经典的设计中,众所周知的是类似 “O” 的圆形字符需要略大于矩形字符,只是因为我们的视觉会认为圆形略小与矩形。如下图所表现的效果。 ![气泡](https://box.kancloud.cn/2015-10-01_560cda10eb83d.png "气泡") *圆形虽然看起来更小,但它的边界实际上和正方形是一致的。* **类似的视觉错觉在视觉设计中非常常见**,往往需要进行适当的修正。一个非常常见的例子就是包含文字的容器内边距。这一问题的原因是容器忽略了文本的数量,可能是一个单词,也可能是好几段文字。如果我们为容器的四个边指定了相同的内边距,那么就会像下图所示: ![气泡](https://box.kancloud.cn/2015-10-01_560cda1129beb.png "气泡") *为容器的四个内边距添加相同的数值(`.5em`),但是容器内的文字让上下两边的内边距显得比左右两边更大一些。* 容器看起来并不协调。究其原因就是**在水平方向上字体形状更加连贯,导致我们的眼睛认为垂直方向上的多余空间都是内边距**。因此,我们在垂直方向上减少内边距,才能让四边的内边距看起来一致。如下图所示: ![气泡](https://box.kancloud.cn/2015-10-01_560cda115d4d9.png "气泡") *让左右两边的内边距更大一些(`.3em .7em`),整体看起来更协调了。* ## 响应式设计 [RWD (Responsive Web Design,响应式设计)](http://www.w3cplus.com/responsive)在过去几年很流行。然而,大家的焦点往往放在了响应式网站的重要性上,而忽略了什么才是响应式网站的优势。 开发响应式网站,常见的方式就是在多种分辨率上测试一个网站,然后通过不断地叠加媒体查询修正出现的问题。然而,每一次使用媒体查询都增加了未来维护 CSS 的负担,它们实际上并不值得被添加。未来维护这段 CSS 代码的开发者需要反复检查是否所有的媒体查询都生效了,而且还可能会去修改这些媒体查询,而这么繁多的细节往往又容易被遗忘,继而导致了某些断层。你添加的媒体查询语句越多,那么 CSS 的碎片化现象就会越严重。 这并不是说使用媒体查询是一个糟糕的方式。**使用方法得当,才会事半功倍。**不过,媒体查询应该是我们的终极解决方案,只有当其他方法都无法完成响应式设计,或者需要在或大或小的视区上完全更改页面布局时(比如将侧边栏转换为水平方向),才应该使用媒体查询。之所以这么说,是因为媒体查询本质上不是用来修复连续性错误的。媒体查询主用于指定视区的阈值(或者称之为断点),除非其他的代码也是灵活可扩展的,否则媒体查询只能在特定的分辨率起作用,本质上并没有解决问题。 不必多说相信你也会理解,**媒体查询的断点不应该由具体的设备来决定,而应该由设计本身来决定。**一方面是因为不同尺寸的设备太多(特别是我们需要为未来的设备而考虑),网站需要尽可能地适应各种分辨率;另一方面是因为在电脑桌面上可能以任意尺寸的窗口打开网页。如果你自信地认为自己的设计可以应用到各种窗口大小,那又何必担心某些特定设备的分辨率呢? 根据第九页“缩简代码”一节的原则来编写媒体查询,将会让你免于不断地重写断点内的样式,减少断点内的问题。 这里有一些技巧,可以让你避免添加无用的媒体查询: * 使用百分比而不是固定宽度。如果不能使用百分比,那么就是用与视窗(Viewport)相关的单位(`vw`, `vh`, `vmin`, `vmax`),它们能够根据视区的宽度或高度生成相应的数值。 * 如果你想为分辨率更高的页面使用固定宽度,那么请使用 **`max-width`**,而不要使用 **`width`**,这样做的好处是即使没有使用媒体查询,仍然能够确保页面适配较低的分辨率。 * 不要忘记将可替换元素设为 **`max-width: 100%;`**,常见的可替换元素包括:**`img`**、**`object`**、**`video`** 和 **`iframe`**。 * 当需要将背景图填充整个容器时,使用 **`background-size: cover;`** 可以让图片在容器改变大小时仍然保持填充,增加代码的可维护性。不过,一定要牢记带宽不是无限的,在移动端页面加载大尺寸的图片,再使用 CSS 进行缩放是非常不明智的做法。 * 在栅格布局中添加图片或者其他元素时,最好浏览器根据视区的宽度来决定列数。使用弹性盒布局([Flexible Box,又被成为 Flexbox](http://www.w3cplus.com/blog/tags/157.html))、声明为**`display: inline-block`** 或者让文本环绕图片,都可以达到这一目的。 * 当使用多栏布局时,应该使用 **`column-width`** 而不是 **`column-count`**,这样的好处即使分辨率很低,内容也不会拥挤在一起,而会正常排布在一列之中。 通常来说,我们需要坚守的原则就是:**在媒体查询的断点中,使用流式布局和相对大小**。当设计足够灵活时,创建响应式布局并不会使用过多的媒体查询语句。在 2010 年末,Basecamp 的设计师关于这一话题写道: > “可以证明的是,只需要在最终的产品上增加少许的 CSS 媒体查询语句,就可以让布局适应各种设备。如此简单的关键在于,布局本身就是流式的。所以对于视区较窄的小尺寸屏幕,我们所要做的就是压缩外边距,增加可利用空间,以及更改侧边栏布局。” —— [Experimenting with responsive design in Iterations](https://signalvnoise.com/posts/2661-experimenting-with-responsive-design-in-iterations) 如果你发现自己需要大量的媒体查询语句适应不同尺寸的屏幕,那么你需要重新审视一下自己的代码,因为十有八九,问题的根据不在于响应式的设计。 > 建议在媒体查询中使用 `em` 而不是 `px`。使用 `em` 可以在布局发生改变时自动进行缩放。 ## 明智地使用简写形式 对于下面的两行代码,你可能会知道它们之间的差异: ~~~ background: rebeccapurple; background-color: rebeccapurple; ~~~ 前者是一个简写写法,它会创建一个颜色为 **`rebeccapurple`** 的背景,而后者(**`background-color`**)可能会得到一个粉色的渐变、一只猫的背景图……这是因为在后者之外,可能还存在一个 **`backgound-image`** 属性在发挥作用。这就是使用普通写法(longhands,比如这里使用的 **`background-size`**)时常会发生的问题:无法重设其他的属性,继而影响了最终的效果。 当然,你可以重设其他所有的属性(这里就不演示了),但你很有可能会有所遗漏。或者,CSS WG 在未来会引入更多的普通写法属性,那么你的代码又会失效。除非刻意地为某些元素使用普通写法属性,比如我们在第九页“缩简代码”一节中对按钮颜色所做的处理,否则不要畏惧缩写形式,它们是优秀的防御性代码,可以有效在未来保持可用性。 组合使用普通写法和简写写法也很有用。当某些属性的属性值是由逗号分隔的参数列表,那么使用组合方式就可以让减少代码中的重复,比如 **`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; ~~~ 请注意,其中 **`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; ~~~ 现在,我们只需要修改一次,就可以改变 **`background-size`** 和 **`background-repeat`** 属性了。 ## 我应该使用预处理器吗? 你也许已经听过类似 [LESS](http://lesscss.org/)、[Sass](http://sass-lang.com/) 和 [Stylus](http://learnboost.github.io/stylus) 等预处理器的大名了。它们为编写 CSS 提供了许多方便的特性,比如变量、混合宏、函数、嵌套规则、颜色操纵方法等等。 **合理使用预处理器,可以在大型项目中保持代码的简洁和灵活性**,而原生的 CSS 由于功能的缺乏往往限制了我们的开发。当我们坚持代码的健壮性、灵活性以及简洁时,我们就会时常受制于语言的能力。此外,预处理器本身也有一些缺陷: * 无法追踪 CSS 文件的大小和复杂性。简洁短小的代码经过预处理器可能就会编译出冗杂的 CSS 代码来。 > 冷知识:怪异的简写语法 你可能已经注意到了,在简写写法和普通写法的示例中,为 **`background`** 简写写法指定**`bacground-size`** 时,需要额外添加 **`background-position`** 属性(虽然属性值就是默认值),并且需要使用反斜线分隔它们。为什么此类简写写法的语法如此怪异呢? 这么做主要是为了消除歧义。虽然在这个示例中 **`top right`** 很明显是 **`background-position`** 的属性、**`2em 2em`** 是 **`background-size`** 的属性,而且这些属性也是和顺序无关的。但是,对于 **`50% 50%`**,你觉得它是 **`background-size`** 的属性,还是 **`background-position`** 的属性呢?当你使用普通写法时,CSS 解析器能够了解你的意思,但是使用普通写法时,解析器就无法从 **`50% 50%`** 这一属性推断出它所指向的属性了。这就是在这里使用反斜线的目的了。 对于大多数的简写写法,很少有这种歧义,而且属性的顺序也没有限制。不过,让属性值的顺序对应合理的语法是一种非常好的做法,可以有效避免混淆和错误。如果你熟悉正则表达式和语法,那么也可以根据相关规范查看语法属性——这可能是判断属性值是否有特定顺序的最快方法。 * 因为在开发者工具中看到的 CSS 并不是你写的 CSS(由预处理器编译生成的),调试错误变得更加困难。鉴于可以通过 SourceMaps 获得调试支持,这已经不是什么难题了。SourceMap 是一个很棒的新技术,它可以告知浏览器生成的 CSS 在预处理器中的 CSS 中的行号,以此来缓解这个问题。 * 在开发流程中,使用预处理器具有一定的延迟。虽然它们的编译速度很快,但仍然会占用一定的时间编译 CSS,而在编译结束前你只能等待。 * 参与到我们代码库中的人,需要付出更多的努力以理解预处理器中的概念。对于我们的合作者,要么他们是熟悉预处理器的人,要么我们就必须教他们使用。所以我们对合作伙伴的选择受到了限制,不然我们就得花费额外的时间训练他们,二者两者都不是最优的方案。 * 不要忘记抽象泄露法则的教诲:“所有有意义的抽象,在一定程度上都是有漏洞的。”预处理器是人类开发的,就像人类开发的其他非凡语言,它们都是有漏洞的——这些漏洞往往难以察觉,而我们往往不会怀疑预处理器出错了,而会怀疑是 CSS 写错了。 除了上述列出的问题,预处理器也让开发者深深地依赖上了它们,即使在某些无需使用预处理器的小项目中,开发者也会惯性使然地使用它们,而开发者所不知道的,预处理器的大多特性都会在未来添加到原生的 CSS 中。惊喜吗?是的,**许多预处理器引以为傲的特性都会被融入原生的 CSS 中:** * 已有一个类似变量的自定义属性草案被提出([CSS Custom Properties for Cascading Variables](http://www.w3.org/TR/css-variables-1/) ) * 源自 CSS Values & Units Level 3 的函数 **`calc()`** 不仅功能强大,而且也广受支持。 * [CSS Color Level 4](http://dev.w3.org/csswg/css-color) 中的 **color()** 方法就会提供操纵颜色的功能。 * 在 CSS WG 中已经就规则嵌套开展了多场严肃的讨论,并形成了一份草案。 请注意,上述提到的原生特性通常比预处理器提供的特性更加强大,因为它们是动态的。比如,预处理器是无法计算类似 **`100% - 50px`** 的表达式,因为在页面渲染完成前,预处理器是无法知道这里的百分比对应的具体数值是多少。与之相比,原生 CSS 的**`calc()`** 方法则可以轻松胜任这项工作。相同的是,预处理器的变量也无法像下面一样使用: ~~~ ul { --accent-color: purple; } ol { --accent-color: rebeccapurple; } li { background: var(--accent-color); } ~~~ 你能理解这里做了什么吗?在有序列表中,列表项的背景色将会使用 **`rebeccapurple`**,而在无序列表中,列表项的背景色将会使用 **`purple`**。试试看预处理能不能做到!在这个示例中,虽然我们只使用了后代选择器,但重点在于展示原生 CSS 变量的动态性。 [![气泡](https://box.kancloud.cn/2015-10-01_560cda11a13c3.png "气泡")](http://myth.io/) *[Myth](http://myth.io/)是一个处于实验阶段的预处理器,它专注于模拟原生 CSS 的特性,而不是引入新的语法,非常类似于一个 CSS 的腻子脚本。* 因为上述的原生 CSS 特性尚未获得良好的支持,所以在大多数情况下使用预处理器是不可避免的。我的建议是在项目中以纯 CSS 起步,当不能保持代码的简洁时,切换为预处理器。为了避免完全依赖预处理器或在不适合的项目中使用预处理器,所以你要慎重的使用它们,而不是盲目的在新项目之初就使用它们。 如果你们想知道的话,那么我要告诉你接下章节中示例的样式就是使用 SCSS 编写的,虽然最初使用的是纯 CSS,但当代码变得复杂之后,为了可维护性切换为了 SCSS。谁说 CSS 和预处理器只能用于 Web?