/ CSS

将HTML和CSS解耦

多年来,Web 标准社区一直在谈论关注分离(separation of concerns),就是将 CSS 、JavaScript、HTML 分开放在各自的文件里。我们也已经这样做了。

CSS Zen Garden (CSS 禅意花园)证明相同的 HTML 结构可以通过改变 CSS 来实现许多不同设计,但我们只看到了一面。我们在项目中经常可能发生的是: HTML 结构变化。我们修改了 HTML ,然后不得不回头去修正受其影响的 CSS 。

这样的话,二者并没有真正分开,不是吗?因为不得不在两个地方做更改。

方法探索

在我的职业生涯中,我有幸参与了数百个网站和 Web 应用程序开发。绝大多数项目中,从事构建 HTML 和 CSS 只有我一个开发者。我养成了一个对我而言很有效的编码方式。

最近,有两年的时间我在 Yahoo 进行 Mail 、Messenger、Calendar 及其他项目相关工作。在更大的团队参与更大的项目是很美好的经历。一个小的原型设计团队和一个更大的设计师团队,为多个团队的工程师构建 HTML 和 CSS。

就很多方面而言,这是我参加过的最大规模的项目:

  • Yahoo 用户基数巨大,仅邮箱用户就近 3 亿;
  • 横跨多个团队的数百个人进行 HTML 及 CSS 制作;
  • 我们开发的是跨项目的系统组件。
我在 Yahoo 这段时间,我开始检验我和团队构建网站的方式。我们遇到了什么弊端、应该如何避免?

我查阅了别人的处理的方式,看了 Nicole Sullivan 的 Object-Oriented CSS 、Jina Bolton 关于 CSS Workflow 的演讲、Natalie Downe 的 Practical, Maintainable CSS 等,仅列出一部分。

最终我将我的想法写成了名为 Scalable and Modular Architecture for CSS 的指南,简称 SMACSS (发音 smacks)。这是我工作过程中逐步形成的、经过提炼和扩展的关于 CSS 开发的指南。

经过这段探索,我注意到包括我在内的设计师,习惯把用于样式设计的 CSS 与 HTML 深度关联在一起。如何才能将二者解耦,以较少的重构实现更灵活的开发呢?换言之,如何避免到处都是 !important 或者掉进选择符地狱呢?

样式重用

曾几何时,每个需要样式化的元素包裹着 font 标签然后应用 background 属性。这相当不可取,因此 CSS 诞生了。使用 CSS 可以在页面的不同部分重用样式。

例如,导航菜单都有一个类似的条目列表。重复地在每个条目上应用内联样式是不可取的。于是,我们采用类似下面的 CSS :

#nav {
   margin: 0;
   padding: 0;
   list-style: none;
}

#nav li {
   float: left;
}

#nav li a {
   display: block;
   padding: 5px 10px;
   background-color: blue;
}

这将为每个列表项应用 float:left ,为每个连接应用 display:block; padding:5px 10px; ,非常高效。预期的 HTML 结构如下:

<ul id="nav">
   <li><a href="/">Home</a></li>
   <li><a href="/products">Products</a></li>
   <li><a href="/contact">Contact Us</a></li>
</ul>

现在,客户又跑来说:我想在客户点击 Products 时显示下拉菜单,以方便他们访问每个页面。于是 HTML 结构改变了:

<ul id="nav">
   <li><a href="/">Home</a></li>
   <li><a href="/products">Products</a>
      <ul>
         <li><a href="/products/shoes">Shoes</a></li>
         <li><a href="/products/jackets">Jackets</a></li> 
      </ul>
   </li>
   <li><a href="/contact">Contact Us</a></li>
</ul>

产品列表项中嵌套了一个无序列表,水平的导航条添加了一个垂直下拉菜单,得添加一些规则以达到客户想要的效果:

#nav ul {
   margin: 0;
   padding:0;
   list-style:none;
}

#nav li li {
   float: none;
}

#nav li li a {
   padding: 2px;
   background-color: red;
}

问题某种程度上解决了。

控制适用范围深度

CSS 中最普遍的问题可能就是管理特殊性。在页面中,会有多条 CSS 规则争着样式化某个元素。上面的例子中,初始规则不仅应用到了导航条上的水平条目,也应用到了下拉菜单。这不太好。

通过添加更多的元素选择符,以增加下拉菜单的样式的特殊性达到了预期效果。但是,随着项目复杂性增加,这会变成一个猫和老鼠的游戏。取而代之,我们应该限制 CSS 规则影响范围。水平导航样式、下拉菜单样式,都应该分别只应用到属于各自元素,不应该影响到其他元素。

我在 SMACSS 中提到了这个影响 - “depth of applicability”,即某条 CSS 规则影响到周围元素的深度。比如说, #nav li a 这样的样式应用到上面包含下拉菜单的 HTML 结构,其适用范围深度为5:从 ulli 再到 ul 再到 li 最后到 a

适应范围越深,HTML 和 CSS 耦合越紧密。易管理的 CSS 的目标,特别是在大项目中,是限制规则的适用范围深度。换句话说,就是编写只应用到想应用样式的元素的 CSS。

子选择符

限制 CSS 作用域的一个工具是子选择符 (>)。如果不考虑 IE6 (谢天谢地,我们中的许多人可以不用再考虑了——国内的苦逼前端们就羡慕嫉妒恨吧!),就可以在 CSS 中常规使用子选择符了。

回到刚才的例子,可以使用子选择符限制水平导航列表项的规则,不至于影响到下拉菜单:

#nav {
   margin: 0;
   padding: 0;
   list-style: none;
}

#nav > li {
   float: left;
}

#nav > li > a {
   display: block;
   padding: 5px 10px;
   background-color: blue;
}

对于下拉菜单,可以添加一个更具描述性的类名,为样式提供钩子。

.menu {
   margin: 0;
   padding: 0;
   list-style: none
}

.menu > li > a {
   display: block;
   padding: 2px;
   background-color: red;
}

上面的调整中完成了限制 CSS 作用域分离两个视觉模式到不同的 CSS 块中:水平导航列表项及下拉菜单。这就朝模块化代码、将 HTML 和 CSS 解耦迈出了一步。

类名化代码

限制适用范围深度有助于最小化样式对更深层 HTML 元素的影响。但是,另一个问题是,一旦在 CSS 中使用元素选择符,就意味着依赖于 HTML 保持不变。上面的导航和下拉菜单,经常是一个无序列表里面嵌套更多层级的无序列表。对这些模块没有灵活性可言。

来看另外一个设计中常出现的例子:一个包含标题和一段主体内容的模块。

<div class="box">
   <h2>Sites I Like</h2>
   <ul>
      <li><a href="http://smashingmagazine.com/">Smashing Magazine</a></li>
      <li><a href="http://smacss.com/">SMACSS</a></li>
   </ul>
</div>

给它添加一些样式。

.box {
   border: 1px solid #333;
}

.box h2 {
   margin: 0;
   padding: 5px 10px;
   border-bottom: 1px solid #333;
   background-color: #CCC;
}

.box ul {
   margin: 10px;
}

假如客户又跑过来说:这个模块看起来不错,能否再添加一个包含网站简介的模块?

<div class="box">
   <h2>About the Site</h2>
   <p>This is my blog where I talk about only the bestest things in the whole wide world.</p>
</div>

刚才的模块中,ul 应用了 margin 以和标题保持一定距离。加了新模块后,需要将样式改为:

.box ul, .box p {
   margin: 10px;
}

如果网站内容不再改变的话,这样也可以。然而,现实中内容经常变动。因此应该前瞻性意识到这个问题,选择一个更好的替代方案。

为了让 CSS 更具灵活性,可以为模块的不同部分添加类:

.box .hd { }  /* this is our box heading */
.box .bd { }  /* this is our box body */

然后应用到 HTML 结构:

<div class="box">
   <h2 class="hd">About the Site</h2>
   <p class="bd">This is my blog where I talk about only the bestest things in the whole wide world.</p>
</div>

明确意图

页面上的不同元素可能有标题和主体部分,作为 box. 的后代选择符它们被“罩着”。但是如果不看 HTML 的话这就不是很明显,应该明确 hd 、bd 类名从属于 box 模块。
.box .box-hd {}
.box .box-bd {}
使用这种改进的命名方式,就不用再在 CSS 中混合选择符了。最终样式为:
.box {
border: 1px solid #333;
}

.box-hd {
margin: 0;
padding: 5px 10px;
border-bottom: 1px solid #333;
background-color: #CCC;
}

.box-bd {
margin: 10px;
}


这样做的好处是这些规则仅对单一元素有效,即应用了类名的元素。CSS 代码更加容易阅读和调试,明确了从属关系和样式目的。

一切才刚开始

刚刚看到的就是将 HTML 和 CSS 解耦的两种方式:
  1. 使用子选择符
  2. 使用类选择符
另外,还展示了简洁、明了、快速、易理解的命名约定。这仅仅是 “Scalable and Modular Architecture for CSS,” 的一部分概念,更多请点击链接查看。

附言

除上面链接的资源意外,你可能对 BEM 感兴趣——一种创建可维护 CSS 的方式和框架。Mark Otto (Twitter Bootstrap 创建者) 最近的一篇文章也讨论了限制样式作用域 - “Stop the Cascade

via Decoupling HTML From CSS - Smashing Magazine