从试着改进可重用CSS做起
回想起每次更新泊学网站,最让我头疼的,就是改写CSS。在不同的阶段,对CSS不断深入的理解,对网站内容的调整,对UI的重用需求,都影响着CSS的编写方式,因此,稍不留神,你的代码理解就会充斥着各种风格和各种作用的CSS,让你什么时候想起这些,都觉得心情不那么愉快。
因此,就和大家分享一些心得,如何理解CSS,以及如何更有效的编写CSS。
从基于语义的CSS说起
首先,我们从一个最简单的例子开始,回想一下你的第一个CSS例子,一定和下面这样是类似的,所谓CSS,表达的就是页面DOM的样式:
<p class="text-center">
Hello world!
</p>
然后,在text-center
里,我们指定文字居中对齐的样式:
.text-center {
text-align: center;
}
很简单对不对?随着样式越写越多,我们很快就会开始关注到一些编写CSS的建议。例如:应该把HTML和CSS的职责分开,HTML中不应该包含任何和具体样式(例如居中对齐)有关的信息,这些具体的样式都应该放到CSS中处理。
于是,我们就开始尝试着用样式要表达的语意来替换掉它表达的具体样式:
<p class="greeting">
Hello world!
</p>
<style>
.greeting {
text-align: center;
}
</style>
这样看起来就好多了。无论.greeting
指定的具体样式是什么,都不影响它在HTML中表示欢迎信息样式的含义。这样,从理论上说,我们就可以用一套HTML模板,实现各种不同风格的UI了。
于是,我们就开始基于这种语义的方式,来编写各种界面了。例如,我们添加一个表示视频作者的信息卡,它的HTML模板是这样的:
<div class="container">
<div class="creator-info">
<img src="http://7xncmx.com1.z0.glb.clouddn.com/dora11.png" alt="">
<div>
<h2>Mars</h2>
<p>
The creator of boxue.io. Bla bla bla...
</p>
</div>
</div>
</div>
同样,在这个模板里,creator-info
是一个按语义命名的样式,接下来,是这个样式的实现:
.creator-info {
background-color: white;
border: 1px solid rgba(0,0,0,0.1);
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
overflow: hidden;
> img {
display: block;
width: 100%;
height: auto;
}
> div {
padding: 1rem;
> h2 {
font-size: 1.25rem;
color: rgba(0,0,0,0.8);
}
> p {
font-size: 1rem;
color: rgba(0,0,0,0.75);
line-height: 1.5;
}
}
}
它看上去的结果是这样的:
这里,我们的重点不是这些样式的具体内容,而是这个CSS的结构,如果我们把所有具体的样式都去掉,你就会发现,这个样式严重依赖于HTML中DOM的层次结构:
.creator-info
> img
> div
> h2
> p
因此,尽管在HTML中,我们依靠基于语义的样式剥离了CSS,但这种方式却很容易在CSS中暴露过多和HTML相关的细节。因此,这样的做法,实际上并没有完全实现剥离CSS和HTML职责的目的,我们需要更好的做法。
把样式从DOM结构中剥离出来
为了避免样式依赖DOM结构的问题,我们的思路是:让样式的命名方式兼具格式和语义的功能。然后,在DOM里,对不同位置的元素,使用对应的样式。这里,我们借鉴了BEM命名方法,对我们要使用的样式名称,统一使用这样的命名格式:主体-依赖主体的内容__内容的属性:
<div class="container">
<div class="creator-info">
<img class="creator-info__image"
src="http://7xncmx.com1.z0.glb.clouddn.com/dora11.png" alt="">
<div class="creator-info__content">
<h2 class="creator-info__name">Mars</h2>
<p class="creator-info__description">
The creator of boxue.io. Bla bla bla...
</p>
</div>
</div>
</div>
这次,我们给DOM中,每一个需要样式的元素绑定了有特定命名规则的样式。这样,在样式表里,所有的样式就可以是扁平结构的了:
.creator-info {
background-color: white;
border: 1px solid rgba(0,0,0,0.1);
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
overflow: hidden;
}
.creator-info__image {
display: block;
width: 100%;
height: auto;
}
.creator-info__content {
padding: 1rem;
}
.creator-info__name {
font-size: 1.25rem;
color: rgba(0,0,0,0.8);
}
.creator-info__description {
font-size: 1rem;
color: rgba(0,0,0,0.75);
line-height: 1.5;
}
还记得当时自己把泊学网站样式修改成这样之后,着实兴奋了一阵子,因为这样的方式似乎彻底解决了HTML模板和CSS之间相互依赖的问题。
处理重复的界面布局
但是没过多久,我就发现了新的问题。当我编写首页上每个视频系列的UI组件时,结构上,它和之前的作者信息卡几乎是一样的。于是我几乎不假思索的写出了这样的HTML模板:
<div class="container">
<div class="series-info">
<img class="series-info__image"
src="https://dn-boxueio.qbox.me/YourFirstMLProject@2x-911fdc56906f05fc0757e5577084c840.jpg"
alt="">
<div class="series-info__content">
<h2 class="series-info__name">Machine Learning from Scratch</h2>
<p class="series-info__description">
Let's create a real-world machine learning demo from scratch.
</p>
</div>
</div>
</div>
它同样包含了一个封面图,一个标题和一个简介。只不过,我们把样式名称中的主体从creator
换成了series
。但是,当我要给这些新的样式设置值的时候,就有点儿纠结了。该如何设置这些series-***
的样式呢?你可能想到了两种选择。
第一种,最直接的方法,就是把series-***
按照creator-***
复制一遍。这肯定可以工作,但是估计没多少人会认同这种做法,因为它违反了Don't Repeat Yourself的原则;
第二种,如果你使用了SCSS,就可以实现从某个样式继承这样的用法:
.series-info {
@extend .creator-info;
}
.series-info__image {
@extend .creator-info__image;
}
.series-info__content {
@extend .creator-info__content;
}
.series-info__name {
@extend .creator-info__name;
}
.series-info__description {
@extend .creator-info__description;
}
但这样做也有它自己的问题,@extend
应该只在彼此有关联的样式之间使用,而不仅仅是为了避免重复编写相同的样式。并且,如果稍后我们还要视频信息卡呢?真的需要这些使用了相同样式的selector么?显然,目前的这种解决方案仍旧不够理想。
去除掉过于细致的语义
实际上,造成样式难以重用的原因,是因为selector表达的语义过于细致了。语义越细致,重用就越困难。因此,我们只要把这种绑定类似界面布局UI的selector,起个名字替代掉类似creator
或series
这样的名字就好了:
.media-card {
background-color: white;
border: 1px solid rgba(0,0,0,0.1);
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
overflow: hidden;
}
.media-card__image {
display: block;
width: 100%;
height: auto;
}
.media-card__content {
padding: 1rem;
}
.media-card__name {
font-size: 1.25rem;
color: rgba(0,0,0,0.8);
}
.media-card__description {
font-size: 1rem;
color: rgba(0,0,0,0.75);
line-height: 1.5;
}
这样,无论是作者信息还是视频系列信息,就都可以用同一套样式来表示了:
<div class="container">
<div class="media-card">
<img class="media-card__image"
src="https://dn-boxueio.qbox.me/YourFirstMLProject@2x-911fdc56906f05fc0757e5577084c840.jpg" alt="">
<div class="media-card__content">
<h2 class="media-card__name">Machine Learning from Scratch</h2>
<p class="media-card__description">
Let's create a real-world machine learning demo from scratch.
</p>
</div>
</div>
</div>
甚至,只要UI布局和media-card
描述的体系相同,这套样式就可以直接重用。
但是情到此结束了么?显然没有,现在,你可能又会想了:假设我们需要修改作者信息卡的样式,但仍保存视频系列信息卡的样式该怎么办呢?
如果像之前一样,它们的样式是独立的,只修改对应的样式就好了。现在,它们共享样式了,我不仅要创建新的样式,还要连同对应的HTML一起修改,这样做真的好么?
实际上根本没有绝对的职责分离
为了回答这个问题,我们得回到这一节开始提出的目的:分离HTML和CSS的职责。面对这个话题,我们直觉上就会认为,只有彻底剥离了才算完成达成目标。但实际的情况则是,它们两者根本无法做到完全分离。我们只能根据自己项目的实际情况,选择一种适合自己的方式。
对于哪些具备详细语义(.creator-info
和.series-info
)的样式而言,此时,HTML是独立的,它完全不关心这些DOM会长成什么样子。它只暴露了一个接口,允许我们定制其中的样式。因此,这种选择下的CSS不是独立的,它依赖于样式绑定的HTML,需要以HTML为参考,定义样式的内容。
对于那些具备中立语义(.media-card
)的样式而言,此时,CSS是独立的,它完全不关心自己会被用在什么元素上。此时,HTML就不是独立的了,它需要知道样式表提供了哪些内容,并基于这些内容,来编排DOM。
实际上,这两种方法,没有绝对的谁优谁劣的问题。只是你要想清楚,哪种方式更适合自己的项目。
What's next?
看到这里,如果你和我之前有过类似的困惑,现在,你应该跃跃欲试地要调整下自己的CSS了。先别着急,在下一节里,我们将继续讨论,如何通过合理的命名,最大化实现样式的可重用目标。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。