提到 CSS,很多 Web 开发人员都不陌生。它是在 Web 应用中控制展现的标准技术。CSS 与 HTML 和 JavaScript 一起,构成了 Ajax 应用的基础。对于 CSS,已经有很多图书和文章进行过详细的介绍。本文不介绍 CSS 语法的细节,而是从一些实际开发中会遇到的问题出发,讨论一些与具体实践相关的话题。
下面首先介绍 CSS 中的一些重要概念,可以帮助读者加深对 CSS 的理解。本文中以 CSS 2.1 规范来进行说明。
CSS 的重要概念
CSS 的语法非常简单,包含的元素也很少,主要有“@ 规则”和样式规则集。“@ 规则”是以@
开头的规则声明,常用的有@import
、@media
和@charset
等。样式规则集是一系列样式声明规则的集合。每个样式规则集由选择器和声明两部分组成。
声明是 CSS 中样式属性的名值对,其形式是“属性名称 : 属性值
”。如声明“font-color : red
”把样式font-color
的值设为red
。
属性继承
对于 CSS 中的某些样式属性来说,如果元素没有显式的指定一个值,该属性就会继承该元素的父元素的这个属性的值。常见的会被继承的属性有:visibility
、color
、font
和text-decoration
等。需要注意的是,发生属性继承的时候,子元素继承的是父元素属性的计算值,而不是指定值。比较典型的例子是通过em
或是百分数指定的字体大小。比如父元素的字体大小的声明是“font-size : 1.2em
”,而实际计算出来的字体大小是 12px,则子元素继承的是 12px,而不是 1.2em。通过设置属性的值为inherit
可以让该属性强制继承其父元素对应属性的值。
在介绍完 CSS 的重要概念之后,下面介绍 CSS 规则的层叠顺序。
CSS 规则的层叠顺序
正如 CSS 的全称“层叠式样式表(Cascading Style Sheets)”所表示的含义一样,CSS 中的样式声明规则是有层叠顺序的。可以在不同的地方把相同的规则应用在相同的元素上面。比如对某个 P 元素,某条 CSS 规则将其文本颜色设为红色,而另外一个规则把其文本颜色设为蓝色。显然这两条规则是互相冲突的。在具体显示的时候,浏览器会根据层叠顺序来判断应用哪条规则,从而确定该 P 元素的文本颜色。层叠顺序的基本原则很简单,那就是越具体的规则,优先级越高。浏览器根据优先级高低来确定应用哪条规则。具体的来说,一条规则的优先级与其使用的选择器和所在的位置相关。下面分别进行说明。
根据所使用选择器的优先级顺序
在判断一条规则的优先级时,会首先判断其所用的选择器的优先级顺序。按照优先级从高到低的顺序排列如下:
- 使用了
!important
声明的规则。 - 内嵌在 HTML 元素的
style
属性里面的声明。 - 使用了 ID 选择器的规则。
- 使用了类选择器、属性选择器、伪元素和伪类选择器的规则。
- 使用了元素选择器的规则。
- 只包含一个通用选择器的规则。
当浏览器需要判断应用互相冲突的规则中的哪条规则的时候,首先会根据上面的顺序来判断优先级。优先级高的规则就会被应用。如果优先级相同,则需要进一步判断所使用的选择器的个数。选择器数目较多的规则优先级较高。比如下面两条规则:#myDiv .header .main {}
和#myDiv .header span {}
,前者的优先级更高。因为前者有一个 ID 选择器,两个类选择器;而后者也有一个 ID 选择器,但是只有一个类选择器。上述的优先级判断算法类似于一般的数字大小比较,首先比较最高位,如果相同的话,再比较次高位,以此类推。
如果按照上面的优先级顺序来判断,规则的优先级相同的话(即所用的选择器类别相同,同类选择器的数量也相同),则需要进一步根据规则所在的位置来判断。
根据规则所在位置的优先级顺序
对于规则所在的不同位置,可以按照优先级从高到低排列如下:
- 在 HTML 文档的
head
元素中的style
元素中定义的规则。 - 通过
style
元素中的@import
语句引入的样式表中定义的规则。 - 通过
link
元素引入的样式表中定义的规则。 - 在
link
元素引入的样式表中,再通过@import
语句引入的样式表中定义的规则。 - 最终用户提供的样式表中定义的规则。
- 浏览器默认提供的规则。
如果规则所在的位置相同的话,出现在样式表后面的规则的优先级更高。
通过这样的两套规则机制,就保证了浏览器可以解决互相冲突的规则的优先级问题。下面通过一个示例来具体说明层叠顺序的机制。
层叠顺序示例
演示层叠顺序的示例所使用的 HTML 和 CSS 如代码清单 1所示。
清单 1. 层叠顺序示例
<style> * { color : black; } p { color : gray; } .p_red{ color : red; } p.p_blue { color : red; } #p1 { color : green; } .p_blue { color : blue; } .p_blue2 { color : blue !important;} </style> <body> <p id="p1" class="p_red"> 示例文本 1</p> <p class="p_red"> 示例文本 2</p> <p class="p_blue"> 示例文本 3</p> <p class="p_blue p_blue2"> 示例文本 4</p> </body>
如代码清单 1所示,HTML 文档中定义了 4 个 P 元素以及一些 CSS 规则用来定义其文本颜色。对于第一个 P 元素来说,可以应用在之上的有两条规则:一条是通过 ID 选择器定义的,另外一条是通过类选择器定义的。由于 ID 选择器优先级高,第一条规则被应用,所以文本颜色是绿色;第二个 P 元素只能应用一个类选择器,所以文本颜色是红色;第二个 P 元素有两个规则可以被应用。规则p.p_blue
比.p_blue
多一个元素选择器,前者被应用,所以文本颜色是红色;最后一个 P 元素所能应用的规则中,p_blue2
使用了!important
声明,优先级最高,因此文本颜色是蓝色。
对于上面提到的两套规则机制,一个重要的例外就是用户样式表中的包含的带!important
的声明具有最高的优先级,超过网站本身提供的样式表中带!important
声明的规则。这样设计的意图是允许用户使用自己提供的样式来覆盖网站提供的样式,以提高网站的可访问性。
熟悉了 CSS 规则的层叠顺序之后,就可以解决常见的 CSS 样式被覆写的问题。很多时候,尤其在团队开发中,一个 CSS 文件可能被多个开发人员修改。经常会遇到的一个问题是,自己的 CSS 样式没有起作用。通过 Firebug 可以发现,原因是自己的 CSS 样式被其他人写的样式给覆写了。根本原因是自己样式声明中使用的选择器的优先级较低。解决这个问题的办法就是查看与你的样式发生冲突的 CSS 规则所使用的选择器,并相应提升自己的选择器的优先级,防止自己的样式被覆写。
在介绍完 CSS 规则的层叠顺序之后,下面介绍 CSS 布局中重要的概念:盒模型。
盒模型
盒模型(Box model)是 CSS 中进行页面布局的重要概念。页面上文档树中的每个元素在显示的时候,都会对应生成一到多个矩形盒子。布局是根据这些盒子来进行的。盒子之间可以互相嵌套。对于一个元素来说,其产生的盒子如图 1所示。
图 1. CSS 的盒模型示意图
从图 1中可以看到,一个盒子从里到外有四个层次:最里面的是内容区域,包含元素本身的内容;紧接着是填充区域;然后是边框;最后是空白区域。内容区域的宽度和高度的确定比较复杂,需要综合考虑样式width
和height
的值,以及该盒子中包含的其它盒子的类型。填充区域、边框和空白区域的上、下、左、右四个方位的宽度都可以通过样式padding
、border
和margin
来分别指定。通过样式background
指定的背景颜色或图片可以应用在内容区域、填充区域和边框上。空白区域总是透明的,不会应用背景样式。
在介绍完盒模型之后,下面介绍如何对页面上的元素进行布局和定位。
布局和定位
浏览器在渲染 HTML 页面的时候,所进行的操作就是把上面提到的文档树上的元素所对应的盒子,按照一定的规则进行排列。下面将详细介绍布局的规则以及元素是如何在页面中定位的。
盒子类型
不同的盒子类型在布局时的行为不一样。盒子的类型是由生成它的元素的类型来决定的。通过样式display
可以指定元素的类型。该样式可以使用的值很多,主要的有block
、inline
、inline-block
和none
等。样式display
的值为block
的元素称为块级元素(block-level element),它们所生成的盒子是块盒子;inline
或inline-block
的元素称为行内元素(inline-level element),它们所生成的是行内盒子。块盒子与行内盒子的具体说明如下:
包围块
在对页面上的元素进行定位的时候,包围块(containing block)是个很重要的概念。很多时候都需要根据其包围块来确定某元素对应的盒子的位置和大小。包围块的确定并不复杂:对于文档根元素,其包围块是浏览器的窗口;如果元素的样式position
的值是static
或relative
,则其包围块是最近的块级祖先元素的内容区域;如果样式fixed
,则其包围块是浏览器的当前视口(viewport);如果样式absolute
,则首先在祖先元素中寻找样式absolute
、relative
或fixed
的元素。找到这个元素之后,如果该元素是行内元素,则包围块由所生成的盒子的填充边界来确定,如果是块级元素,则包围块是该元素的填充区域。
元素的定位由样式position
的值来确定,该样式的默认值是static
,其它可选的值有relative
和fixed
。样式position
的值为static
的值称为非定位元素,其它的称为定位元素。
在 CSS 中,一个盒子可能有三种定位方式:正常文档流、浮动和绝对定位。通过样式position
和float
的值可以控制所采用的定位方式。
正常文档流
正常文档流是按照盒子的类型来定位的。每个盒子都会属于某个格式化上下文中,块或是行内的。块格式化上下文会按照从上到下的垂直顺序来排列盒子。行内格式化上下文则按照水平顺序来排列盒子,从左到右还是从右到左取决于文档的顺序。
通过将样式position
的值设为relative
,可以把当前元素的盒子设成相对定位的。相对定位的盒子首先按照正常文档流来排列,然后再根据其样式left
、right
、top
和bottom
的值来相对原始位置进行偏移。在它之后出现的盒子不受偏移的影响,就好像它还在原始位置一样。相对定位盒子的一个常见的作用是作为其绝对定位的子盒子的包围块。
浮动定位
通过将元素的样式float
的值设为left
或right
,可以使得该元素对应的盒子在当前行上向左或向右浮动。浮动的盒子会尽可能的向左或向右移动,直到接触到包围块或是另外一个浮动盒子的边界。如果当前行没有足够的空间给浮动盒子,该盒子会往下移动直到有足够的空间为止。
浮动的盒子不属于正常文档流的一部分。在浮动盒子之前和之后的非定位块盒子按照其在正常流中的位置排列,相当于浮动盒子不存在一样。而浮动盒子之后的行内盒子则会被缩短,紧跟在浮动盒子的后面。所形成的效果就是后面的行内盒子包围着浮动的盒子。在网页中文字环绕图片的效果就可以通过这种方式来实现。通过样式clear
可以强制盒子不跟在浮动盒子的后面。
绝对定位
将样式absolute
或fixed
,使得元素对应的盒子变成绝对定位。绝对定位的盒子被从正常文档流中完全移除,对相邻的盒子没有任何影响。绝对定位的盒子的位置是相对其包围块来确定的。具体的位置由样式bottom
来确定。
在介绍完 CSS 中的布局和定位之后,下面介绍如何解决不同浏览器之间 CSS 样式的兼容性问题。
浏览器兼容性
在使用 CSS 为网页添加样式的时候,不得不面对的一个棘手的问题就是浏览器的兼容性所带来的复杂性。不同的浏览器对于 CSS 规范本身的理解,以及具体的实现都有很大不同。浏览器本身还可能存在各种各样的 bug。在 Firefox 上面显示正常的网页,用 IE 打开的话就可能发现整体的布局错位。相同浏览器的不同版本之间的差别也会很大。开发人员需要花费大量的时间和精力来保证网页在各种浏览器上呈现的样式是一致的。一般来说,解决浏览器兼容性问题有三种做法:利用浏览器本身的支持、使用招数和使用 JavaScript。下面先介绍浏览器本身的支持。
@H_854_301@ 浏览器本身的支持可以利用浏览器本身支持的能力,在一定程度上解决兼容性的问题。比较典型的是 IE 提供的条件注释。通过条件注释,可以在页面由特定版本的 IE 来显示的时候,设置特殊的样式,如代码清单 2所示。
清单 2. IE 提供的条件注释
代码清单 2中条件注释中包含的样式和引入的 CSS 文件ie6.css
,只对 IE 6 才起作用。下面介绍招数。
招数
招数(hack)是利用浏览器本身对 CSS 规范支持的不完善或是实现上的 bug,来针对特定的浏览器应用样式的一些做法。这些招数通常用来满足这样的需求,即对某个或某些浏览器应用特定的样式。IE 6 不支持直接后代选择器,即不识别div > span
这样的选择器。如果想实现这样的效果:IE 6 上面的某个span
元素的左空白边界的宽度设为 20 像素,而其它浏览器的设为 10 像素,可以利用上面提到的招数来实现,具体如代码清单 3所示。
清单 3. 招数示例
如代码清单 3所示,首先通过浏览器都支持的后代选择器来设定样式margin-left
的值为20px
,然后再通过直接后代选择器在其他浏览器上面覆写此样式。由于 IE 6 不识别选择器div > span
,就不会应用此样式。
一般来说,可以使用的招数分成两类:一类是利用浏览器对 CSS 规范支持的不完善,如上面给出的直接后代选择器的例子;另外一种是浏览器本身实现的 bug,也就是说浏览器没有按照预期的行为来工作。比如 IE 6 不认为html
元素是文档的根元素,在html
之外还有另外一个匿名的元素。因此可以利用* html div
这样的选择器来设置只对 IE 6 起作用的样式。推荐的做法是使用第一类招数。它的好处是如果浏览器升级了,支持了原来不支持的特性,CSS 样式还能正常工作;而如果使用第二类招数,浏览器升级的时候修正了招数所依赖的 bug,那么 CSS 样式就不起作用了。
由于招数可能随着浏览器的升级而变得不可用,建议只在必要的时候才使用它。需要使用招数的时候,也尽量借助已有的CSS 框架。下面介绍使用 JavaScript 代码来解决兼容性的问题。
使用 JavaScript
在有些时候,单纯依靠浏览器本身的支持,以及已有的招数,并不能完全解决 CSS 样式在不同浏览器上面的兼容性问题。这个时候可以通过 JavaScript 来解决。通过 JavaScript 可以判断当前的浏览器类型,从而应用不同的样式。一般来说,有两种典型的实现方式。
一种做法是直接在 JavaScript 代码中修改 CSS 样式。很多 JavaScript 库都提供了这样的能力,如 Dojo 就提供了很好的支持。对于上面的例子,用 Dojo 解决的做法如代码清单 4所示。
清单 4. Dojo 的浏览器检查与样式修改
如代码清单 4所示,Dojo 提供了浏览器检测和直接修改 CSS 样式的功能。这样的话,在 CSS 里面只需要添加对绝大多数浏览器都适用的样式声明。对于个别浏览器的少量样式调整,可以在 JavaScript 代码里面来完成。相对于直接修改样式来说,另外一种更好的做法是添加额外的 CSS 类声明,如dojo.addClass("span_ie6")
。使用 CSS 类声明,会使得代码的可维护性更好。
另外一种做法是用 JavaScript 为页面根元素添加一个特殊的 CSS 类。比如用 IE 6 访问使用了 Dojo 的页面,页面根元素会添加 CSS 类dj_ie
和dj_ie6
。对于上面的例子,这种做法如代码清单 5所示。
清单 5. 为页面根元素添加 CSS 类
在介绍完浏览器的兼容性问题之后,下面介绍如何开发出可维护的 CSS 文件。
可维护的 CSS
前面提到过,CSS 本身的语法非常简单,包含的元素非常少。但是要编写模块清晰,可维护性高的 CSS 样式表,并非一件容易的事情,尤其对于样式繁多的复杂站点来说。一种比较好的解决办法是把面向对象的思想引入到 CSS 样式表的编写过程中,用它来指导 CSS 样式的组织。具体的指导原则有组件化和单一职责。
组件化
用面向对象的思想解决复杂问题的一种常用做法是组件化,即把复杂的对象拆分成若干小的组件。每个组件相对独立,用来解决更小的问题。这样既降低了组件的开发难度,也提高了复用性。具体到 CSS 样式表来说,就是开发出针对页面上某类元素的样式组件。这些样式组件可以在不同的页面中任意组合使用。常用的样式组件有针对标题(h1
-h6
)、列表(ul
和ol
)和按钮等元素的。样式组件并不限于 HTML 中的元素,也可以是页面上的逻辑单元,比如 iGoogle 个性化页面上的每个窗口小部件。
单一职责
在面向对象的开发中,通常鼓励的一种实践是为每个对象分配单一的职责,从而使得它只会因为一种原因而改变。这种实践对 CSS 样式表也适用。具体来说就是把结构和外观分开,让它们分别变化。比如网页上的一个窗口小部件,从结构上来说,需要定义其标题栏和内容区域的大小和位置,还有右上角的按钮的位置;从外观上来说,需要定义文本的字体大小、颜色以及背景图片等。通过把描述结构和外观的样式分开,可以提高可维护性。当需要为窗口小部件提供一套新的配色方案时,只需要修改外观样式即可。
多文件组织
如果使用上面提到的面向对象的思想来组织 CSS 样式表,会得到多个 CSS 文件,每个文件里面包含一到多个样式组件。多个 CSS 文件相对于单一文件来说,可读性和可维护性都更好。在开发新页面的时候,只需要根据页面内容的需要,引入包含所需样式组件的 CSS 文件即可。通过@import
可以很容易的做到这一点。比如页面中使用了标题、列表还有窗口小部件,可以用代码清单 6中给出的方式引入 CSS。
清单 6. 引入多个 CSS 文件
以分开的方式单独引入多个 CSS 文件可能会带来性能上的问题,对此可以使用工具来进行压缩。
在介绍完可维护的 CSS 之后,下面介绍一些帮助开发 CSS 的框架和工具。
框架与工具
在编写网页的过程中,使用已有的成熟的 CSS 框架与工具,可以减少开发的工作量,也更容易解决浏览器的兼容性问题。下面介绍一些常用的 CSS 框架和工具。
YUI CSS
作为 JavaScript 库之一的 YUI 也提供了一些基础的 CSS 样式,可以供开发人员使用。这些样式分成三类,分别是重置 CSS、字体 CSS 和基本 CSS。通常来说,浏览器会为网页提供一个默认的样式表。如果开发人员的样式表中没有定义某些属性的值的话,浏览器在显示的时候就会使用自己的样式。不同浏览器的默认样式是不同的,这样会导致网页在不同浏览器上的显示有所不同。重置 CSS 的功能就是通过显式的样式声明,来覆写浏览器的默认样式。开发人员的样式表以重置之后的样式作为基础,就保证了不同浏览器上的一致性。YUI 提供的重置 CSS 只是一个简单的 CSS 文件,可以直接在网页中引用。YUI 字体 CSS 可以为网页设置默认的字体,以及调整字体的大小。YUI 基本 CSS 为一些常用的 HTML 元素提供了基本样式。关于 YUI CSS 的更多信息,见参考资料。
Blueprint
Blueprint 是一个复杂的 CSS 框架,提供了多个 CSS 文件,所提供的样式包括重置、网格、字体、表单、IE 支持和打印等。这里值得一提的是它的网格样式。绝大多数网页采用的都是某种网格型的布局,比如常见的两列布局和三列布局等。对于简单的两列或三列布局,通过简单的 CSS 样式就可以实现。如果列的数目过多,或是存在嵌套列的话,自己开发和维护的代价就太高了。使用 Blueprint 的网格样式可以很容易的实现,并且可维护性和灵活性更好。Blueprint 的网格样式提供了 24 个列。元素可以选择横跨任意多个列。比如只需要为元素添加 CSS 类span-8
就声明该元素横跨 8 个列。关于 Blueprint 的更多信息,见参考资料。
CSS 文件压缩
相对于 JavaScript 文件的压缩来说,CSS 文件的压缩并没有那么的流行。其原因可能是一般网站的 CSS 文件数量较少,单个文件的体积也较小。相对于 JavaScript 文件来说,CSS 文件对性能的影响要小得多。但是如果应用上面提到的可维护的 CSS实践的话,会产生较多的 CSS 文件。过多的 CSS 文件意味着过多的 HTTP 请求,对网站性能会有较大的影响。目前已经有不少的工具支持对 CSS 文件的压缩,如 YUI 压缩器和 Minify(见参考资料)。
在介绍了其它的 CSS 框架和工具之后,下面介绍 Dojo 提供的对 CSS 的支持。
Dojo 的支持
Dojo 对于 CSS 的支持,主要体现在修改元素的样式、获取元素的定位信息和辅助页面布局上。下面分别加以介绍。
修改样式
Dojo 提供了一些方法用来对 Ajax 应用中元素的样式进行修改。
dojo.style
用来获取元素的样式,以及对样式进行修改。该方法的声明是dojo.style(node,style,value)
,其中node
表示的是元素的引用或是 ID;style
表示要获取或设置的样式的名称,也可以是一个包含“样式 / 值”名值对的 JSON 对象;value
表示的是要设置的样式的值。传入两个参数可以是获取单个样式的值,也可以是设置一组样式的值,如用来获取字体的大小,
用来同时设置字体大小和颜色。传入三个参数用来设置单个样式的值,如
用来设置字体大小。
一般推荐的做法是不直接设置样式的值,而是修改元素的 CSS 类,这样 CSS 样式规则的可维护性更好。Dojo 也提供了几个方法用来修改元素的 CSS 类,分别是:
dojo.addClass(node,classStr)用来添加新的CSS类。
dojo.removeClass(node,classStr)用来删除已有的CSS类。如果第二个参数为空的话,则删除所有的CSS类。
dojo.toggleClass(node,classStr,condition)用来切换CSS的出现状态。如果元素已有了classStr指定的CSS类,则删除掉这些类;否则的话就添加这些类。参数condition用来显示的声明是添加还是删除,传入true表示执行添加操作。
dojo.hasClass(node,classStr)用来判断元素是否已经包含了某些CSS类。
获取定位信息
在有些时候,需要用 JavaScript 来获取和设置页面元素所对应的盒子的大小和位置。Dojo 提供了两个方法dojo.marginBox(node,Box)
和dojo.contentBox(node,Box)
用来获取和设置页面元素的空白区域和内容区域的大小和位置。这两个方法的第一个参数node
表示的是元素的 ID 或引用;第二个参数Box
是可选的,不提供此参数表示是获取,提供的话则表示的是设置。Box
是一个 JSON 对象,包含l
、t
、w
和h
等 4 个属性,分别表示盒子相对于父元素左上角在水平方向的偏移量、在垂直方向的偏移量、宽度和高度。通过这两个方法获取大小和位置的时候,其返回值与Box
的格式相同。
辅助页面布局
在进行页面设计的时候,通常会使用一些经典的布局样式,比如常用的“标题栏 + 侧边栏 + 内容 + 底部栏”的布局。这些布局使用手工编写的 CSS 样式或是Blueprint都可以很容易的实现。如果网页中使用了 Dojo 的话,利用它提供的布局容器组件是个不错的选择。它可以很好的与网页上的其它 Dojo 组件结合在一起。布局可以使用的容器组件是dijit.layout.BorderContainer
。它把布局的区域划分成 5 个部分,分别是上、下、左、右和中央,只需要把其它组件放在对应的部分就可以了。上面提到的布局的 Dojo 实现如代码清单 7所示。
清单 7. 使用 Dojo 实现的布局
如代码清单 7所示,通过属性region
可以声明每个组件(dijit.layout.ContentPane
)在整个布局中的位置。通过splitter="true"
可以添加一个用来改变大小的分割器。
总结
正如之前一直说到的,CSS 本身语法非常简单,但是要在复杂的 Ajax 应用中合理的编写和组织 CSS 样式表,是需要很多经验积累才能做好的一件事情。本文并没有对 CSS 的语法细节做过多的介绍,而是重点说明了 CSS 语法中复杂的部分。除此之外,还讨论了 CSS 样式表的浏览器兼容性问题的解决办法。接着介绍了编写可维护的 CSS 的一些原则。最后介绍了 CSS 框架和工具,以及 Dojo 中与 CSS 相关的内容。