SMACSS 学习笔记
19 Dec 2014写出可用的 CSS 并不难,但是写出可维护的 CSS 一直是我在考虑的一个问题。如何让一个团队写出来的 CSS 像是同一个人写出来的,在这点上,Google CSS style guide 规定得太少。而 CSS 模式可以解决这种问题,本文就是我在学习 SMACSS( Scalable and Modular Architecture for CSS ) 时候的笔记。
理想中的 CSS 模式
先有一个直观的感受,什么才是可维护的 CSS
/* Base */
body, form {
margin: 0;
padding: 0;
}
a {
color: #039;
}
a:hover {
color: #03F;
}
/* Layout */
.l-fixed {
width: 960px;
margin: auto;
}
.l-content {
width: 200px;
float: left;
}
.l-sidebar {
width: 740px;
float: left;
margin-left: 20px;
}
/* Module */
.menu-horizontal > li {
display: inline-block;
}
.menu-items {
list-style: none;
}
.menu-items > .is-active {
text-decoration: underline;
}
.menu-featuredItems {
padding-left: 20px;
}
.menu-header {
font-weight: bold;
text-transform: uppercase;
}
/* Status */
.is-hidden {
display: none !important;
visibility: hidden !important;
}
.pull-left {
float: left
}
.pull-right {
float: right
}
.clearfix:after {
content: " ";
visibility: hidden;
display: block;
height: 0;
clear: both;
}
CSS 的目录划分
- Base
- 基本概念:使用元素、伪类、子元素、后代选择器指定默认样式,不规定 ID 和 class 样式
- 主要功能:设置标题大小,默认链接样式、默认字体样式、页面背景
- 注意:不应在 base 里使用
!important
;应当始终设置
- Layout
- 基本概念:划分页面的几个部分(如 Header, Sidebar, Content, Footer)
- 注意:一般使用单一选择器(ID 或 class 均可),尽可能使用 ID (因为会给 JavaScript 提供便利)
- 若想要设置多种不同风格的 layout 供用户选择,可在 layout 的父层级上指定类型(如 body),然后再用后代选择器指定样式,如
.l-fixed #article
- Module
- 基本概念:可重用的组件
- 注意:避免使用 ID 选择器
- 使用语义化的类名,如
.fld-name
、fld-items
;避免使用元素选择器 -
同一 module 在不同情景下的表现区分,可用 sub-class 的方式来实现。如:
.pod { width: 100%; } .pod input[type=text] { width: 50%; } .pod-constrained input[type=text] { width: 100%; }
- 需要考虑 specificity 的时候,可以将类名叠加起来,如
.pod.pod-callout
- State
- 基本概念:规定指定 module 和 layout 在特定状态下的样式
- State 样式具有最高优先级,可覆盖其他任何样式
- State 和 sub-class 区别于两点:
- State 可以应用在 layout 和 module 上
- State 通常意味着 JavaScript 依赖
- State 可(应)使用
!important
,因为不会出现同时有两个相反的状态出现在同一元素上的情形 - 当某种状态和某个 module 高度相关时,应考虑在命名上加以区分,如
.is-tab-active
- Theme
- 基本概念:规定 layout 和 module 的样式
命名规则
- layout 和 state 使用前缀
.layout-
或.l-
.is-hidden
、.is-collapsed
- module 使用语义,模块内元素使用前缀
.example
、.callout
.example-caption
状态变化
一般来讲,元素样式变化可通过三种状态变化进行:
- 类
- 一般通过 JavaScript 根据用户行为来变化
- 可通过给指定元素加减 State 类使其变化
-
对于子菜单,可用兄弟选择器来实现,会使动作更易扩展,对其他的元素影响更小,如:
<div id="content"> <div class="toolbar"> <button id="btn-new" class="btn is-active" data-action="menu">New</button> <div id="menu-new" class="menu"> <ul> ... </ul> </div> </div> </div>
/* CSS for styling */ .btn.is-active { color: #000; } .btn.is-active + .menu { display: block; }
-
用属性选择器实现状态变化,如:
.btn[data-state=default] { color: #333; } .btn[data-state=pressed] { color: #000; } .btn[data-state=disabled] { opacity: .5; pointer-events: none; }
<button class="btn" data-state="disabled">Disabled</button>
// bind a click handler to each button $(".btn").bind("click", function(){ // change the state to pressed $(this).attr('data-state', 'pressed'); });
-
JavaScript 负责行为,可以描述状态变化,不应该用来添加内联样式(inline styles);CSS 负责表现。两者配合实现动画:
@-webkit-keyframes fade { 0% { opacity:0; } 100% { opacity:1; display:block; } } .is-visible { opacity: 1; animation: fade 2s; } .is-hidden { opacity: 0; animation: fade 2s reverse; } .is-removed { display: none; }
function showMessage (s) { var el = document.getElementById('message'); el.innerHTML = s; /* set state */ el.className = 'is-visible'; setTimeout(function(){ /* set state back */ el.className = 'is-hidden'; setTimeout(function(){ el.className = 'is-removed'; }, 2000); }, 3000); }
- 伪类
- 注:可通过伪类的变化来改变元素的兄弟(siblings)和后代。要想改变其他,还得使用 JavaScript
- 使用了 sub-class 以后也要加上相应的伪类
- 媒体查询(media query)
-
使用响应式设计的时候,在每个的 module 下立刻写出对应的 media query,保证 module 的集中,如:
/* default state for nav items */ .nav > li { float: left; } /* alternate state for nav items on small screens */ @media screen and (max-width: 400px) { .nav > li { float: none; } } ... elsewhere for layout ... /* default layout */ .content { float: left; width: 75%; } .sidebar { float: right; width: 25%; } /* alternate state for layout on small screens */ @media screen and (max-width: 400px) { .content, .sidebar { float: none; width: auto; } }
-
选择器的深度(Depth)
- CSS 不应依赖与 HTML 的结构;被选择的 HTML 元素位置不应太深。一个反例:
body.article > #main > #content > #intro > p > b
- 当层级过深时,应该把相同属性抽象出来,成为一个 module 或 sub-class