多年来,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 制作;
- 我们开发的是跨项目的系统组件。
我查阅了别人的处理的方式,看了 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:从 ul
到 li
再到 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 解耦的两种方式:- 使用子选择符
- 使用类选择符
附言
除上面链接的资源意外,你可能对 BEM 感兴趣——一种创建可维护 CSS 的方式和框架。Mark Otto (Twitter Bootstrap 创建者) 最近的一篇文章也讨论了限制样式作用域 - “Stop the Cascade”。via Decoupling HTML From CSS - Smashing Magazine