本文来自学习JavaScript DOM编程艺术一书时的笔记和总结等。
CSS-DOM技术针对的是如何得到和设置style对象的各种属性,而style对象本身又是文档中的每个元素节点都具备的属性。不过,只要有可能,就应选择更新className属性,而不是去更新style对象的有关属性。如果想改变某个元素的呈现效果,使用CSS;如果想改变某个元素的行为,使用DOM;如果你想根据某个元素的行为去改变它的呈现效果,请运用你的智慧,在这个问题上没有标准答案。
接下来,将主要学习CSS-DOM的以下几个技术知识:
- 根据元素在节点树里的位置设置它们的样式
- 遍历一个节点集合设置有关元素的样式
- 在事件发生时设置有关元素的样式
1、关于网页的分层知识
- 结构层,使用(X)HTML去搭建文档的结构;
- 表示层,使用CSS去设置文档的呈现效果;
- 行为层,使用DOM脚本去实现文档的行为。
2、style属性
- 文档的每个元素节点都有一个style属性,style属性包含着元素的样式。样式都存放在这个style对象的属性里。
- 查询这个属性将返回一个对象而不是一个简单的字符串。
- style对象的各个属性都是可读写的。
一个样例标记文件:
<p id="example" style="color: grey; font-family: 'Arial',sans-serif;">
An example of a paragraph
</p>
访问element.style.color可以获得style对象的color属性;
访问element.style.fontFamily可以获得style对象的font属性;
注:当你需要引用一个中间带减号的CSS属性时,DOM要求你使用驼峰命名法。
需要注意的一点是:DOM style属性不能用来检索在外部CSS文件里声明的样式,style对象只包含在HTML代码里用style属性声明的样式。
但如果你是用DOM设置的样式,就可以再用DOM把它们检索出来。
可以使用赋值操作来更新元素的样式:
element.style.property = value
例如:
para.style.color = "black";
para.style.font = "2em 'Times'serif";
注:上面语句是同时赋值了fontSize和fontFamily两个属性
3、通过CSS声明样式的方法主要有三种
(1) 为标签元素统一地声明样式
p {
font-size: 1em;
}
(2) 为有特定class属性的所有元素统一声明样式
.fineprint {
font-size: .8em;
}
(3) 为有独一无二的id属性的元素单独声明样式
#intro {
font-size: 1.2em;
}
(4) 还有两类变种方法
可以为有类似属性的多个元素声明样式:
input[type*="text"] {
font-size: 1.2em;
}
在现代浏览器中,可以根据元素的位置来声明样式:
p:first-of-type {
font-size: 2em;
font-weight: bold;
}
CSS2引入了很多位置相关的选择器,例如:first-child和:last-child
CSS3则定义了:nth-child()和:nth-of-type()之类的位置选择器。在CSS3中还可以使用h1~*选择器为所有h1元素的下一个同辈元素声明样式。
问题:仍然有很多的浏览器,不支持CSS3的这些位置选择器功能。
4、根据元素在节点树里的位置来使用DOM脚本设置样式
目前,CSS还无法根据元素之间的相对位置关系人找出某个特定的元素。但这对于DOM却是轻而易举的。
样例:定义一个名为styleHeaderSiblings的JS函数,来为文档中所有h1元素后的那个元素设置一个特殊的样式。
标记文件story.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Man bites dog</title>
</head>
<body>
<h1>Hold the front page</h1>
<p>This first paragraph leads you in.</p>
<p>Now you get the nitty-gritty of the story.</p>
<p>The most important information is delivered first.</p>
<h1>Extra Extra!</h1>
<p>Further developments are unfolding.</p>
<p>You can read all about it here.</p>
</body>
</html>
页面浏览效果如下图所示:
我们现在编写一个名为styleHeaderSiblings.js脚本文件,以实现对h1标题后第一个元素节点的样式设置。
function sytleHeaderSiblings() {
if (!document.getElementsByTagName) return false;
var headers = document.getElementsByTagName("h1");
var elem;
for (var i=0; i<headers.length; i++) {
elem = getNextElement(headers[i].nextSibling)
elem.style.fontWeight = "bold";
elem.style.fontSize = "1.2em";
}
}
//确保获取到的下一个是元素节点
function getNextElement(node) {
if (node.nodeType == 1) {
return node;
}
if (node.nextSibling) {
return getNextElement(node.nextSibling);
}
return null;
}
addLoadEvent(sytleHeaderSiblings);
将该js函数加载到标记文件中并随浏览器打开自动被调用,效果如下:
5、使用DOM技术根据某种条件反复设置某种样式
这种情况最为典型的应用安全是,对一个表格让表格里的行交替改变它们的颜色,从而形成斑马线效果,便于阅读。
如果浏览器支持CSS3的话,有一种非常简便的方法如下:
tr:nth-child(odd) { background-color:#ffc; }
tr:nth-child(even) { background-color:#fff; }
鉴于仍然有很多浏览器对CSS3的支持不够好,显然使用JavaScript处理这种重复性任务是一个不错的选择。
可以编写一个函数来为表格添加斑马线效果,只要隔行设置效果就行了:
- 把文档里的所有table元素找出来;
- 对每个table元素,创建odd变量并把它初始化为false;
- 遍历这个表格里的所有数据行;
- 如果变量odd的值是true,设置样式并把odd变量修改为false;
- 如果变量odd的值是false,不设置样式,但把odd变量修改为true。
以下是实现这个样例的标记文件、css文件和js文件。
(1) 标记文件itinerary.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Cities</title>
<link rel="stylesheet" media="screen" href="styles/format.css" />
<script type="text/javascript" src="scripts/addLoadEvent.js"></script>
<script type="text/javascript" src="scripts/stripeTables.js"></script>
<script type="text/javascript" src="scripts/highlightRows.js"></script>
</head>
<body>
<table>
<caption>Itinerary</caption>
<thead>
<tr>
<th>When</th>
<th>Where</th>
</tr>
</thead>
<tbody>
<tr>
<td>June 9th</td>
<td>Portland, <abbr title="Oregon">OR</abbr></td>
</tr>
<tr>
<td>June 10th</td>
<td>Seattle, <abbr title="Washington">WA</abbr></td>
</tr>
<tr>
<td>June 12th</td>
<td>Sacramento, <abbr title="California">CA</abbr></td>
</tr>
</tbody>
</table>
</body>
</html>
(2) css样式文件format.css
body {
font-family: "Helvetica","Arial",sans-serif;
background-color: #fff;
color: #000;
}
table {
margin: auto;
border: 1px solid #699;
}
caption {
margin: auto;
padding: .2em;
font-size: 1.2em;
font-weight: bold;
}
th {
font-weight: normal;
font-style: italic;
text-align: left;
boder: 1px dotted #699;
background-color: #9cc;
color: #000;
}
th,td {
width: 10em;
padding: .5em;
}
(3) js函数stripeTables
function stripleTables() {
if (!document.getElementsByTagName) return false;
var tables = document.getElementsByTagName("table");
var odd, rows;
for (var i=0; i<tables.length; i++) {
odd = false;
rows = tables[i].getElementsByTagName("tr");
for (var j=0; j<rows.length; j++) {
if (odd == true) {
rows[j].style.backgroundColor = "#ffc";
odd = false;
} else {
odd = true;
}
}
}
}
addLoadEvent(stripleTables);
此时套用了css样式,并经由DOM方法加工后的表格呈现出了下面的效果。
6、使用DOM根据响应事件改变元素的样式
在css做得不是很好的领域,DOM可以解决很多问题。
如果是要让链接在鼠标指针悬停在其上时改变颜色,就应该使用css伪类实现:
a:hover {
color: #c60;
}
css伪类:hover已经得到了绝大多数浏览器的支持,至少是在处理改变链接的样式时是这样。
但是如果还想让鼠标指针悬停在其它元素上时改变样式,支持这种用法的浏览器就很少了。不过,DOM仍然可以很容易得做到这一点。
下面的highlightRows函数将鼠标指针悬停在某个表格的行上方时,把该行文本加黑加粗显示:
function highlightRows() {
if (!document.getElementsByTagName) return false;
var rows = document.getElementsByTagName("tr");
for (var i=0; i<rows.length; i++) {
rows[i].onmouseover = function() {
this.style.fontWeight = "bold";
}
rows[i].onmouseout = function() {
this.style.fontWeight = "normal";
}
}
}
addLoadEvent(highlightRows);
7、className属性
在前面的例子中,一直在使用DOM直接设置或修改样式。这种让行为层干表示层的活,并不是理想的工作方式。
这里还有一种简明的解决方案:与其直接使用DOM改变某个元素的样式,不如通过js代码去更新这个元素的class属性。
className是一个可读/可写的属性:element.className = value
以下通过对前面的两个样例进行优化,来应用className属性。
(1) 优化第一步:改造styleHeaderSiblings函数
定义一个外部样式:
.intro {
font-weight: bold;
font-size: 1.2em;
}
js函数改造为:
function sytleHeaderSiblings() {
if (!document.getElementsByTagName) return false;
var headers = document.getElementsByTagName("h1");
var elem;
for (var i=0; i<headers.length; i++) {
elem = getNextElement(headers[i].nextSibling)
elem.className = "intro";
}
}
此时,不论你想什么时候改变一下紧跟在一级标题后的那个元素的样式,只需在css里修改.intro的样式声明。
(2) 优化第二步:封装一个addClass函数
在上一步中通过className属性设置某个样式的class属性时,是完全替换原有样式,而不是追加。如果我们需要的是再叠加一种新样式,则需要定义一个函数,做一些处理工作。
function addClass(element,value) {
if (!element.className) {
element.className = value;
} else {
newClassName = element.className;
newClassName+= " ";
newClassName+= value;
element.className = newClassName;
}
}
注:如果元素原来未指定样式,则直接进行赋值;否则,就把一个空格和新样式追加到className属性上去。
js函数继续改造为:
function sytleHeaderSiblings() {
if (!document.getElementsByTagName) return false;
var headers = document.getElementsByTagName("h1");
var elem;
for (var i=0; i<headers.length; i++) {
elem = getNextElement(headers[i].nextSibling)
addClass(elem,"intro");
}
}
也使用addClass函数改造一下stripeTables这个斑马线函数:
先增加一个样式声明
.odd {
background-color: #ffc;
}
function stripleTables() {
if (!document.getElementsByTagName) return false;
var tables = document.getElementsByTagName("table");
var odd, rows;
for (var i=0; i<tables.length; i++) {
odd = false;
rows = tables[i].getElementsByTagName("tr");
for (var j=0; j<rows.length; j++) {
if (odd == true) {
addClass(rows[j],"odd");
odd = false;
} else {
odd = true;
}
}
}
}
(3) 优化第三步:对函数继续进行抽象
在前两步的优化尝试中仍然是对样式进行的硬编码,写在函数代码里的。下面对styleElementSiblings函数转换为一个更通用的函数,为其增加两个参数——tag和theclass。使其可以灵活得按设计需求决定参数取值。
function sytleHeaderSiblings(tag,theclass) {
if (!document.getElementsByTagName) return false;
var headers = document.getElementsByTagName(tag);
var elem;
for (var i=0; i<headers.length; i++) {
elem = getNextElement(headers[i].nextSibling);
addClass(elem,theclass);
}
}
//确保获取到的下一个是元素节点
function getNextElement(node) {
if (node.nodeType == 1) {
return node;
}
if (node.nextSibling) {
return getNextElement(node.nextSibling);
}
return null;
}
addLoadEvent(function() {
sytleHeaderSiblings("h1","intro");
});
到这一步为止,所有我们能做的优化工作终于都做完了。通过对一个元素样式设置函数的渐进优化,我们最终得到了一个行为和样式分离,又避免了直接在代码中硬编码的实现结果。