??DOM 1 級主要定義的是 HTML 和 XML 文檔的底層結(jié)構(gòu)。
??DOM2 和 DOM3 級則在這個結(jié)構(gòu)的基礎(chǔ)上引入了更多的的交互能力,也支持了更高級的 XML 特性簇宽。
??為此勋篓,DOM2 和 DOM3 級分為許多模塊(模塊之間具有某種關(guān)聯(lián)),分別描述了 DOM 的某個非常具體的子集魏割。這些模塊如下譬嚣。
- DOM2 級核心(DOM Level 2 Core):在 1 級核心基礎(chǔ)上構(gòu)建,為節(jié)點添加了更多方法和屬性钞它。
- DOM2 級視圖(DOM Level 2 Views):為文檔定義了基于樣式信息的不同視圖拜银。
- DOM2 級 HTML(DOM Level 2 HTML):在 1 級 HTML 基礎(chǔ)上構(gòu)建,添加了更多屬性须揣、方法和新接口盐股。
- DOM2 級事件(DOM Level 2 Events):說明了如何使用事件與 DOM 文檔交互。
- DOM2 級樣式(DOM Level 2 Style):定義了如何以編程方式來訪問和改變 CSS 樣式信息耻卡。
- DOM2 級遍歷和范圍(DOM Level 2 Traversal and Range):引入了遍歷 DOM 文檔和選擇其特定部分的新接口疯汁。
??這里探討除 “DOM2 級事件” 之外的所有模塊。
1卵酪、DOM 變化
??DOM2 級和 3 級的目的在于擴(kuò)展 DOM API幌蚊,以滿足操作 XML 的所有需求,同時提供更好的錯誤處理及特性檢測能力溃卡。從某種意義上講溢豆,實現(xiàn)這一目的很大程度意味著對命名空間的支持。
??“DOM2 級核心” 沒有引入新類型瘸羡,它只是在 DOM1 級的基礎(chǔ)上通過增加新方法和新屬性來增強(qiáng)了既有類型漩仙。
??“DOM3 級核心” 同樣增強(qiáng)了既有類型,但也引入了一些新類型犹赖。
??類似的队他,“DOM2 級視圖” 和 “DOM2 級 HTML” 模塊也增強(qiáng)了 DOM 接口,提供了新的屬性和方法峻村。由于這兩個模塊很小麸折,因此我們將把它們與 “DOM2 級核心” 放在一起,討論基本 JavaScript 對象的變化粘昨。
??可以通過下列代碼來確定瀏覽器是否支持這些 DOM 模塊垢啼。
var supportsDOM2Core = document.implementation.hasFeature("Core", "2.0");
var supportsDOM3Core = document.implementation.hasFeature("Core", "3.0");
var supportsDOM2HTML = document.implementation.hasFeature("HTML", "2.0");
var supportsDOM2Views = document.implementation.hasFeature("Views", "2.0");
var supportsDOM2XML = document.implementation.hasFeature("XML", "2.0");
1.1、針對 XML 命名空間的變化
??有了 XML 命名空間张肾,不同 XML 文檔的元素就可以混合在一起芭析,共同構(gòu)成格式良好的文檔,而不必?fù)?dān)心發(fā)生命名沖突吞瞪。
??從技術(shù)上說放刨,HTML 不支持 XML 命名空間,但 XHTML 支持 XML 命名空間尸饺。因此进统,本節(jié)給出的都是 XHTML 的示例。
??命名空間要使用 xmlns 特性來指定浪听。XHTML 的命名空間是 http://www.w3.org/1999/xhtml螟碎,在任何格式良好 XHTML 頁面中,都應(yīng)該將其包含在<html>元素中迹栓,如下面的例子所示掉分。
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Example XHTML page</title>
</head>
<body>
Hello world!
</body>
</html>
??對上述例子而言,其中的所有元素默認(rèn)都被視為 XHTML 命名空間中的元素克伊。要想明確地為 XML 命名空間創(chuàng)建前綴酥郭,可以使用 xmlns 后跟冒號,再后跟前綴愿吹,如下所示不从。
<xhtml:html xmlns:xhtml="http://www.w3.org/1999/xhtml">
<xhtml:head>
<xhtml:title>Example XHTML page</xhtml:title>
</xhtml:head>
<xhtml:body>
Hello world!
</xhtml:body>
</xhtml:html>
??這里為 XHTML 的命名空間定義了一個名為 xhtml 的前綴,并要求所有 XHTML 元素都以該前綴開頭犁跪。有時候為了避免不同語言間的沖突椿息,也需要使用命名空間來限定特性,示例:
<xhtml:html xmlns:xhtml="http://www.w3.org/1999/xhtml">
<xhtml:head>
<xhtml:title>Example XHTML page</xhtml:title>
</xhtml:head>
<xhtml:body xhtml:class="home">
Hello world!
</xhtml:body>
</xhtml:html>
??這個例子中的特性 class 帶有一個 xhtml 前綴坷衍。在只基于一種語言編寫 XML 文檔的情況下寝优,命名空間實際上也沒有什么用。不過枫耳,在混合使用兩種語言的情況下乏矾,命名空間的用處就非常大了。
??來看一看下面這個混合了 XHTML 和 SVG 語言的文檔:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Example XHTML page</title>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 100 100" style="width:100%; height:100%">
<rect x="0" y="0" width="100" height="100" style="fill:red"/>
</svg>
</body>
</html>
??在上述例子中迁杨,通過設(shè)置命名空間钻心,將<svg>標(biāo)識為了與包含文檔無關(guān)的元素。此時仑最,<svg>元素的所有子元素扔役,以及這些元素的所有特性,都被認(rèn)為屬于 http://www.w3.org/2000/svg 命名空間警医。
??即使這個文檔從技術(shù)上說是一個 XHTML 文檔亿胸,但因為有了命名空間,其中的 SVG代碼也仍然是有效的预皇。
??對于類似這樣的文檔來說侈玄,最有意思的事發(fā)生在調(diào)用方法操作文檔節(jié)點的情況下。例如吟温,在創(chuàng)建一個元素時序仙,這個元素屬于哪個命名空間呢?在查詢一個特殊標(biāo)簽名時鲁豪,應(yīng)該將結(jié)果包含在哪個命名空間中呢潘悼?
??“DOM2 級核心” 通過為大多數(shù) DOM1 級方法提供特定于命名空間的版本解決了這個問題律秃。
1. Node 類型的變化
??在 DOM2 級中,Node 類型包含下列特定于命名空間的屬性治唤。
- localName:不帶命名空間前綴的節(jié)點名稱棒动。
- namespaceURI:命名空間 URI 或者(在未指定的情況下是)null。
- prefix:命名空間前綴或者(在未指定的情況下是)null宾添。
??當(dāng)節(jié)點使用了命名空間前綴時船惨,其 nodeName 等于 prefix+":"+ localName
。以下面的文檔為例:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Example XHTML page</title>
</head>
<body>
<s:svg xmlns:s="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 100 100" style="width:100%; height:100%">
<s:rect x="0" y="0" width="100" height="100" style="fill:red"/>
</s:svg>
</body>
</html>
??對于<html>元素來說缕陕,它的 localName 和 tagName 是"html"粱锐,namespaceURI 是 "http://www.w3.org/1999/xhtml",而 prefix 是 null扛邑。
??對于<s:svg>元素而言怜浅,它的 localName 是"svg",
tagName 是"s:svg"鹿榜,namespaceURI 是"http://www.w3.org/2000/svg"海雪,而 prefix 是"s"。
??DOM3 級在此基礎(chǔ)上更進(jìn)一步舱殿,又引入了下列與命名空間有關(guān)的方法奥裸。
- isDefaultNamespace(namespaceURI):在指定的 namespaceURI 是當(dāng)前節(jié)點的默認(rèn)命名空間的情況下返回 true。
- lookupNamespaceURI(prefix):返回給定 prefix 的命名空間沪袭。
- lookupPrefix(namespaceURI):返回給定 namespaceURI 的前綴湾宙。
??針對前面的例子,可以執(zhí)行下列代碼:
console.log(document.body.isDefaultNamespace("http://www.w3.org/1999/xhtml"); // true
// 假設(shè) svg 中包含著對<s:svg>的引用
console.log(svg.lookupPrefix("http://www.w3.org/2000/svg")); //"s"
console.log(svg.lookupNamespaceURI("s")); //"http://www.w3.org/2000/svg"
??在取得了一個節(jié)點冈绊,但不知道該節(jié)點與文檔其他元素之間關(guān)系的情況下侠鳄,這些方法是很有用的。
2. Document 類型的變化
??DOM2 級中的 Document 類型也發(fā)生了變化死宣,包含了下列與命名空間有關(guān)的方法伟恶。
- createElementNS(namespaceURI, tagName):使用給定的 tagName 創(chuàng)建一個屬于命名空間 namespaceURI 的新元素。
- createAttributeNS(namespaceURI, attributeName):使用給定的 attributeName 創(chuàng)建一個屬于命名空間 namespaceURI 的新特性毅该。
- getElementsByTagNameNS(namespaceURI, tagName):返回屬于命名空間 namespaceURI 的 tagName 元素的 NodeList博秫。
??使用這些方法時需要傳入表示命名空間的 URI(而不是命名空間前綴),如下面的例子所示眶掌。
// 創(chuàng)建一個新的 SVG 元素
var svg = document.createElementNS("http://www.w3.org/2000/svg","svg");
// 創(chuàng)建一個屬于某個命名空間的新特性
var att = document.createAttributeNS("http://www.somewhere.com", "random");
// 取得所有 XHTML 元素
var elems = document.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "*");
??只有在文檔中存在兩個或多個命名空間時挡育,這些與命名空間有關(guān)的方法才是必需的。
3. Element 類型的變化
??“DOM2 級核心”中有關(guān) Element 的變化朴爬,主要涉及操作特性即寒。新增的方法如下。
- getAttributeNS(namespaceURI, localName):取得屬于命名空間 namespaceURI 且名為 localName 的特性。
- getAttributeNodeNS(namespaceURI, localName):取得屬于命名空間 namespaceURI 且名為 localName 的特性節(jié)點母赵。
- getElementsByTagNameNS(namespaceURI, tagName):返回屬于命名空間 namespaceURI 的 tagName 元素的 NodeList逸爵。
- hasAttributeNS(namespaceURI,localName):確定當(dāng)前元素是否有一個名為 localName 的特性,而且該特性的命名空間是 namespaceURI凹嘲。注意痊银,“DOM2 級核心”也增加了一個 hasAttribute() 方法,用于不考慮命名空間的情況施绎。
- removeAttriubteNS(namespaceURI,localName):刪除屬于命名空間 namespaceURI 且名為 localName 的特性。
- setAttributeNS(namespaceURI,qualifiedName,value):設(shè)置屬于命名空間 namespaceURI 且名為 qualifiedName 的特性的值為 value贞绳。
- setAttributeNodeNS(attNode):設(shè)置屬于命名空間 namespaceURI 的特性節(jié)點谷醉。
??除了第一個參數(shù)之外,這些方法與 DOM1 級中相關(guān)方法的作用相同冈闭;第一個參數(shù)始終都是一個命名空間 URI俱尼。
4. NamedNodeMap 類型的變化
??NamedNodeMap 類型也新增了下列與命名空間有關(guān)的方法。由于特性是通過 NamedNodeMap 表示的萎攒,因此這些方法多數(shù)情況下只針對特性使用遇八。
- getNamedItemNS(namespaceURI, localName):取得屬于命名空間 namespaceURI 且名為 localName 的項。
- removeNamedItemNS(namespaceURI, localName):移除屬于命名空間 namespaceURI 且名為 localName 的項耍休。
- setNamedItemNS(node):添加 node刃永,這個節(jié)點已經(jīng)事先指定了命名空間信息。
??由于一般都是通過元素訪問特性羊精,所以這些方法很少使用斯够。
1.2、其他方面的變化
??DOM 的其他部分在“DOM2 級核心”中也發(fā)生了一些變化喧锦。這些變化與 XML 命名空間無關(guān)读规,而是更傾向于確保 API 的可靠性及完整性。
1. DocumentType 類型的變化
??DocumentType 類型新增了 3 個屬性:publicId燃少、systemId 和 internalSubset.
??其中束亏,前兩個屬性表示的是文檔類型聲明中的兩個信息段瘸恼,這兩個信息段在 DOM1 級中是沒有辦法訪問到的肯污。以下面的 HTML 文檔類型聲明為例。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
??對這個文檔類型聲明而言世澜,publicId 是"-//W3C//DTD HTML 4.01//EN"怔昨,而 systemId 是 "http://www.w3.org/TR/html4/strict.dtd"雀久。
??在支持 DOM2 級的瀏覽器中,應(yīng)該可以運行下列代碼趁舀。
console.log(document.doctype.publicId);
console.log(document.doctype.systemId);
??實際上赖捌,很少需要在網(wǎng)頁中訪問此類信息。
??最后一個屬性 internalSubset,用于訪問包含在文檔類型聲明中的額外定義越庇,以下面的代碼為例罩锐。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
[<!ELEMENT name (#PCDATA)>] >
??訪問 document.doctype.internalSubset 將得到 "<!ELEMENT name (#PCDATA)>"
。
??這種內(nèi)部子集(internal subset)在 HTML 中極少用到卤唉,在 XML 中可能會更常見一些涩惑。
2. Document 類型的變化
??Document 類型的變化中唯一與命名空間無關(guān)的方法是 importNode()。
??這個方法的用途是從一個文檔中取得一個節(jié)點桑驱,然后將其導(dǎo)入到另一個文檔竭恬,使其成為這個文檔結(jié)構(gòu)的一部分。
??需要注意的是熬的,每個節(jié)點都有一個 ownerDocument 屬性痊硕,表示所屬的文檔。如果調(diào)用 appendChild() 時傳入的節(jié)點屬于不同的文檔(ownerDocument 屬性的值不一樣)押框,則會導(dǎo)致錯誤岔绸。
??但在調(diào)用 importNode() 時傳入不同文檔的節(jié)點則會返回一個新節(jié)點,這個新節(jié)點的所有權(quán)歸當(dāng)前文檔所有橡伞。
??說起來盒揉,importNode() 方法與 Element 的 cloneNode() 方法非常相似,它接受兩個參數(shù):要復(fù)制的節(jié)點和一個表示是否復(fù)制子節(jié)點的布爾值兑徘。返回的結(jié)果是原來節(jié)點的副本刚盈,但能夠在當(dāng)前文檔中使用。示例:
var newNode = document.importNode(oldNode, true); // 導(dǎo)入節(jié)點及其所有子節(jié)點
document.body.appendChild(newNode);
??這個方法在 HTML 文檔中并不常用道媚,在 XML 文檔中用得比較多扁掸。
??“DOM2 級視圖”模塊添加了一個名為 defaultView 的屬性,其中保存著一個指針最域,指向擁有給定文檔的窗口(或框架)谴分。
??除此之外,“視圖”規(guī)范沒有提供什么時候其他視圖可用的信息镀脂,因而這是唯一一個新增的屬性牺蹄。
??除 IE 之外的所有瀏覽器都支持 defaultView 屬性。
??在 IE 中有一個等價的屬性名叫 parentWindow(Opera 也支持這個屬性)薄翅。
??因此沙兰,要確定文檔的歸屬窗口,可以使用以下代碼翘魄。
var parentWindow = document.defaultView || document.parentWindow;
??除了上述一個方法和一個屬性之外鼎天,“DOM2級核心”還為 document.implementation 對象規(guī)定了兩個新方法:createDocumentType() 和 createDocument()。
??前者用于創(chuàng)建一個新的 DocumentType 節(jié)點暑竟,接受 3 個參數(shù):文檔類型名稱斋射、publicId、systemId。例如罗岖,下列代碼會創(chuàng)建一個新的 HTML4.01 Strict 文檔類型涧至。
var doctype = document.implementation.createDocumentType("html",
"-//W3C//DTD HTML 4.01//EN",
"http://www.w3.org/TR/html4/strict.dtd");
??由于既有文檔的文檔類型不能改變,因此 createDocumentType() 只在創(chuàng)建新文檔時有用桑包;
??創(chuàng)建新文檔時需要用到 createDocument() 方法南蓬。這個方法接受 3 個參數(shù):針對文檔中元素的 namespaceURI、文檔元素的標(biāo)簽名哑了、新文檔的文檔類型赘方。下面這行代碼將會創(chuàng)建一個空的新XML 文檔。
var doc = document.implementation.createDocument("", "root", null);
??這行代碼會創(chuàng)建一個沒有命名空間的新文檔弱左,文檔元素為<root>蒜焊,而且沒有指定文檔類型。
??要想創(chuàng)建一個 XHTML 文檔科贬,可以使用以下代碼。
var doctype = document.implementation.createDocumentType("html",
" -//W3C//DTD XHTML 1.0 Strict//EN",
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd");
var doc = document.implementation.createDocument("http://www.w3.org/1999/xhtml",
"html", doctype);
??這樣鳖悠,就創(chuàng)建了一個帶有適當(dāng)命名空間和文檔類型的新 XHTML 文檔榜掌。不過,新文檔當(dāng)前只有文檔元素<html>乘综,剩下的所有元素都需要繼續(xù)添加憎账。
??“DOM2 級 HTML”模塊也為 document.implementation 新增了一個方法,名叫 createHTMLDocument()卡辰。
??這個方法的用途是創(chuàng)建一個完整的 HTML 文檔胞皱,包括<html>、<head>九妈、<title> 和 <body>元素反砌。
??這個方法只接受一個參數(shù),即新創(chuàng)建文檔的標(biāo)題(放在<title>元素中的字符串)萌朱,返回新的 HTML 文檔宴树,如下所示:
var htmldoc = document.implementation.createHTMLDocument("New Doc");
console.log(htmldoc.title); // "New Doc"
console.log(typeof htmldoc.body); // "object"
??通過調(diào)用 createHTMLDocument() 創(chuàng)建的這個文檔,是 HTMLDocument 類型的實例晶疼,因而具有該類型的所有屬性和方法酒贬,包括 title 和 body 屬性。只有 Opera 和 Safari 支持這個方法翠霍。
3. Node 類型的變化
??Node 類型中唯一與命名空間無關(guān)的變化锭吨,就是添加了 isSupported() 方法。
??與 DOM1 級為 document.implementation 引入的 hasFeature() 方法類似寒匙,isSupported() 方法用于確定當(dāng)前節(jié)點具有什么能力零如。
??這個方法也接受相同的兩個參數(shù):特性名和特性版本號。如果瀏覽器實現(xiàn)了相應(yīng)特性,而且能夠基于給定節(jié)點執(zhí)行該特性埠况,isSupported() 就返回 true耸携。示例:
if (document.body.isSupported("HTML", "2.0")){
// 執(zhí)行只有"DOM2 級 HTML"才支持的操作
}
??由于不同實現(xiàn)在決定對什么特性返回 true 或 false 時并不一致,這個方法同樣也存在與 hasFeature() 方法相同的問題辕翰。
??為此夺衍,我們建議在確定某個特性是否可用時,最好還是使用能力檢測喜命。
??DOM3 級引入了兩個輔助比較節(jié)點的方法:isSameNode() 和 isEqualNode()沟沙。
??這兩個方法都接受一個節(jié)點參數(shù),并在傳入節(jié)點與引用的節(jié)點相同或相等時返回 true壁榕。
??所謂相同矛紫,指的是兩個節(jié)點引用的是同一個對象。所謂相等牌里,指的是兩個節(jié)點是相同的類型颊咬,具有相等的屬性(nodeName、nodeValue牡辽,等等)喳篇,而且它們的 attributes 和 childNodes 屬性也相等(相同位置包含相同的值)。示例:
var div1 = document.createElement("div");
div1.setAttribute("class", "box");
var div2 = document.createElement("div");
div2.setAttribute("class", "box");
console.log(div1.isSameNode(div1)); // true
console.log(div1.isEqualNode(div2)); // true
console.log(div1.isSameNode(div2)); // false
??這里創(chuàng)建了兩個具有相同特性的<div>元素态辛。這兩個元素相等麸澜,但不相同。
??DOM3 級還針對為 DOM 節(jié)點添加額外數(shù)據(jù)引入了新方法奏黑。
??setUserData() 方法會將數(shù)據(jù)指定給節(jié)點炊邦,它接受 3 個參數(shù):要設(shè)置的鍵、實際的數(shù)據(jù)(可以是任何數(shù)據(jù)類型)和處理函數(shù)熟史。
??以下代碼可以將數(shù)據(jù)指定給一個節(jié)點馁害。
document.body.setUserData("name", "Nicholas", function(){});
??然后,使用 getUserData() 并傳入相同的鍵蹂匹,就可以取得該數(shù)據(jù)蜗细,如下所示:
var value = document.body.getUserData("name");
??傳入 setUserData() 中的處理函數(shù)會在帶有數(shù)據(jù)的節(jié)點被復(fù)制、刪除怒详、重命名或引入一個文檔時調(diào)用炉媒,因而你可以事先決定在上述操作發(fā)生時如何處理用戶數(shù)據(jù)。
??處理函數(shù)接受 5 個參數(shù):表示操作類型的數(shù)值(1 表示復(fù)制昆烁,2 表示導(dǎo)入吊骤,3 表示刪除,4 表示重命名)静尼、數(shù)據(jù)鍵白粉、數(shù)據(jù)值传泊、源節(jié)點和目標(biāo)節(jié)點。
??在刪除節(jié)點時鸭巴,源節(jié)點是 null眷细;除在復(fù)制節(jié)點時,目標(biāo)節(jié)點均為 null鹃祖。
??在函數(shù)內(nèi)部溪椎,你可以決定如何存儲數(shù)據(jù)。示例:
var div = document.createElement("div");
div.setUserData("name", "Nicholas", function(operation, key, value, src, dest){
if (operation == 1){
dest.setUserData(key, value, function(){});
}
});
var newDiv = div.cloneNode(true);
console.log(newDiv.getUserData("name")); // "Nicholas"
??這里恬口,先創(chuàng)建了一個<div>元素校读,然后又為它添加了一些數(shù)據(jù)(用戶數(shù)據(jù))。在使用 cloneNode() 復(fù)制這個元素時祖能,就會調(diào)用處理函數(shù)歉秫,從而將數(shù)據(jù)自動復(fù)制到了副本節(jié)點。結(jié)果在通過副本節(jié)點調(diào)用 getUserData() 時养铸,就會返回與原始節(jié)點中包含的相同的值雁芙。
4. 框架的變化
??框架和內(nèi)嵌框架分別用 HTMLFrameElement 和 HTMLIFrameElement 表示,它們在 DOM2級中都有了一個新屬性钞螟,名叫 contentDocument却特。
??這個屬性包含一個指針,指向表示框架內(nèi)容的文檔對象筛圆。在此之前,無法直接通過元素取得這個文檔對象(只能使用 frames 集合)椿浓√可以像下面這樣使用這個屬性。
var iframe = document.getElementById("myIframe");
var iframeDoc = iframe.contentDocument; // 在 IE8 以前的版本中無效
??由于 contentDocument 屬性是 Document 類型的實例扳碍,因此可以像使用其他 HTML 文檔一樣使用它提岔,包括所有屬性和方法。Opera笋敞、Firefox碱蒙、Safari 和 Chrome 支持這個屬性。
??IE8 之前不支持框中的 contentDocument 屬性夯巷,但支持一個名叫 contentWindow 的屬性赛惩,該屬性返回框架的 window 對象,而這個 window 對象又有一個 document 屬性趁餐。
??因此喷兼,要想在上述所有瀏覽器中訪問內(nèi)嵌框架的文檔對象,可以使用下列代碼后雷。
var iframe = document.getElementById("myIframe");
var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
??所有瀏覽器都支持 contentWindow 屬性季惯。
??訪問框架或內(nèi)嵌框架的文檔對象要受到跨域安全策略的限制吠各。如果某個框架中的頁面來自其他域或不同子域,或者使用了不同的協(xié)議勉抓,那么要訪問這個框架的文檔對象就會導(dǎo)致錯誤贾漏。
2、樣式
?? HTML 中定義樣式的方式有 3 種:通過<link/>元素包含外部樣式表文件藕筋、使用<style/>元素定義嵌入式樣式纵散,以及使用 style 特性定義針對特定元素的樣式。
??“DOM2 級樣式”模塊圍繞這 3 種應(yīng)用樣式的機(jī)制提供了一套 API念逞。要確定瀏覽器是否支持 DOM2 級定義的 CSS 能力困食,可以使用下列代碼。
var supportsDOM2CSS = document.implementation.hasFeature("CSS", "2.0");
var supportsDOM2CSS2 = document.implementation.hasFeature("CSS2", "2.0");
2.1翎承、訪問元素的樣式
??任何支持 style 特性的 HTML 元素在 JavaScript 中都有一個對應(yīng)的 style 屬性硕盹。這個 style 對象是 CSSStyleDeclaration 的實例,包含著通過 HTML 的 style 特性指定的所有樣式信息叨咖,但不包含與外部樣式表或嵌入樣式表經(jīng)層疊而來的樣式瘩例。
??在 style 特性中指定的任何 CSS 屬性都將表現(xiàn)為這個 style 對象的相應(yīng)屬性。對于使用短劃線(分隔不同的詞匯甸各,例如 background-image)的 CSS 屬性名垛贤,必須將其轉(zhuǎn)換成駝峰大小寫形式,才能通過 JavaScript 來訪問趣倾。下表列出了幾個常見的 CSS 屬性及其在 style 對象中對應(yīng)的屬性名聘惦。
CSS屬性 | JavaScript屬性 |
---|---|
background-image | style.backgroundImage |
color | style.color |
display | style.display |
font-family | style.fontFamily |
??多數(shù)情況下,都可以通過簡單地轉(zhuǎn)換屬性名的格式來實現(xiàn)轉(zhuǎn)換儒恋。其中一個不能直接轉(zhuǎn)換的 CSS 屬性就是 float善绎。由于 float 是 JavaScript 中的保留字,因此不能用作屬性名诫尽。
??“DOM2 級樣式”規(guī)范規(guī)定樣式對象上相應(yīng)的屬性名應(yīng)該是 cssFloat禀酱;Firefox、Safari牧嫉、Opera 和 Chrome 都支持這個屬性剂跟,而 IE
支持的則是 styleFloat。
??只要取得一個有效的 DOM 元素的引用酣藻,就可以隨時使用 JavaScript 為其設(shè)置樣式曹洽。以下是幾個例子。
var myDiv = document.getElementById("myDiv");
// 設(shè)置背景顏色
myDiv.style.backgroundColor = "red";
// 改變大小
myDiv.style.width = "100px";
myDiv.style.height = "200px";
// 指定邊框
myDiv.style.border = "1px solid black";
??在以這種方式改變樣式時辽剧,元素的外觀會自動被更新衣洁。
??在標(biāo)準(zhǔn)模式下,所有度量值都必須指定一個度量單位抖仅。
??在混雜模式下坊夫,可以將 style.width 設(shè)置為"20"砖第,瀏覽器會假設(shè)它是"20px";
??但在標(biāo)準(zhǔn)模式下环凿,將 style.width 設(shè)置為"20"會導(dǎo)致被忽略——因為沒有度量單位梧兼。在實踐中,最好始終都指定度量單位智听。
??通過 style 對象同樣可以取得在 style 特性中指定的樣式羽杰。以下面的 HTML 代碼為例。
<div id="myDiv" style="background-color:blue; width:10px; height:25px"></div>
??在 style 特性中指定的樣式信息可以通過下列代碼取得到推。
console.log(myDiv.style.backgroundColor); // "blue"
console.log(myDiv.style.width); // "10px"
console.log(myDiv.style.height); // "25px"
??如果沒有為元素設(shè)置 style 特性考赛,那么 style 對象中可能會包含一些默認(rèn)的值,但這些值并不能準(zhǔn)確地反映該元素的樣式信息莉测。
1. DOM 樣式屬性和方法
??“DOM2 級樣式”規(guī)范還為 style 對象定義了一些屬性和方法颜骤。這些屬性和方法在提供元素的 style特性值的同時,也可以修改樣式捣卤。下面列出了這些屬性和方法忍抽。
- cssText:如前所述,通過它能夠訪問到 style 特性中的 CSS 代碼董朝。
- length:應(yīng)用給元素的 CSS 屬性的數(shù)量鸠项。
- parentRule:表示 CSS 信息的 CSSRule 對象。
- getPropertyCSSValue(propertyName):返回包含給定屬性值的 CSSValue 對象子姜。
- getPropertyPriority(propertyName):如果給定的屬性使用了!important 設(shè)置祟绊,則返回 "important";否則哥捕,返回空字符串牧抽。
- getPropertyValue(propertyName):返回給定屬性的字符串值。
- item(index):返回給定位置的 CSS 屬性的名稱扭弧。
- removeProperty(propertyName):從樣式中刪除給定屬性。
- setProperty(propertyName, value, priority):將給定屬性設(shè)置為相應(yīng)的值记舆,并加上優(yōu)先權(quán)標(biāo)志("important"或者一個空字符串)鸽捻。
??通過cssText 屬性可以訪問 style特性中的 CSS 代碼。
??在讀取模式下泽腮,cssText 返回瀏覽器對style 特性中 CSS 代碼的內(nèi)部表示御蒲。
??在寫入模式下,賦給 cssText 的值會重寫整個 style 特性的值诊赊;也就是說厚满,以前通過 style 特性指定的樣式信息都將丟失。
??例如碧磅,如果通過 style 特性為元素設(shè)置了邊框碘箍,然后再以不包含邊框的規(guī)則重寫 cssText遵馆,那么就會抹去元素上的邊框。下面是使用 cssText 屬性的一個例子丰榴。
myDiv.style.cssText = "width: 25px; height: 100px; background-color: green";
console.log(myDiv.style.cssText);
??設(shè)置 cssText 是為元素應(yīng)用多項變化最快捷的方式货邓,因為可以一次性地應(yīng)用所有變化。
??設(shè)計 length 屬性的目的四濒,就是將其與 item() 方法配套使用换况,以便迭代在元素中定義的 CSS 屬性。
??在使用 length 和 item() 時盗蟆,style 對象實際上就相當(dāng)于一個集合戈二,都可以使用方括號語法來代替 item() 來取得給定位置的 CSS 屬性,如下面的例子所示喳资。
for (var i=0, len=myDiv.style.length; i < len; i++){
console.log(myDiv.style[i]); // 或者 myDiv.style.item(i)
}
??無論是使用方括號語法還是使用 item() 方法矫废,都可以取得 CSS 屬性名("background-color",不是"backgroundColor")坐昙。然后顾翼,就可以在 getPropertyValue() 中使用取得的屬性名進(jìn)一步取得屬性的值,如下所示宏赘。
var prop, value, i, len;
for (i=0, len=myDiv.style.length; i < len; i++){
prop = myDiv.style[i]; // 或者 myDiv.style.item(i)
value = myDiv.style.getPropertyValue(prop);
console.log(prop + " : " + value);
}
??getPropertyValue() 方法取得的始終都是 CSS 屬性值的字符串表示绒北。
??如果你需要更多信息,可以使用 getPropertyCSSValue() 方法察署,它返回一個包含兩個屬性的 CSSValue 對象闷游,這兩個屬性分別是:cssText 和 cssValueType。其中贴汪,cssText 屬性的值與 getPropertyValue()返回的值相同脐往,而 cssValueType 屬性則是一個數(shù)值常量,表示值的類型:0 表示繼承的值扳埂,1 表示基本的值业簿,2 表示值列表,3 表示自定義的值阳懂。以下代碼既輸出 CSS 屬性值梅尤,也輸出值的類型。
var prop, value, i, len;
for (i=0, len=myDiv.style.length; i < len; i++){
prop = myDiv.style[i]; // 或者 myDiv.style.item(i)
value = myDiv.style.getPropertyCSSValue(prop);
console.log(prop + " : " + value.cssText + " (" + value.cssValueType + ")");
}
??在實際開發(fā)中岩调,getPropertyCSSValue() 使用得比 getPropertyValue() 少得多巷燥。
??IE9+、Safarie3+ 以及 Chrome 支持這個方法号枕。Firefox 7 及之前版本也提供這個訪問缰揪,但調(diào)用總返回 null。
??要從元素的樣式中移除某個 CSS 屬性葱淳,需要使用 removeProperty() 方法钝腺。使用這個方法移除一個屬性抛姑,意味著將會為該屬性應(yīng)用默認(rèn)的樣式(從其他樣式表經(jīng)層疊而來)。例如拍屑,要移除通過 style 特性設(shè)置的 border 屬性途戒,可以使用下面的代碼。
myDiv.style.removeProperty("border");
??在不確定某個給定的 CSS 屬性擁有什么默認(rèn)值的情況下僵驰,就可以使用這個方法喷斋。只要移除相應(yīng)的屬性,就可以為元素應(yīng)用默認(rèn)值蒜茴。
2. 計算的樣式
??雖然 style 對象能夠提供支持 style 特性的任何元素的樣式信息星爪,但它不包含那些從其他樣式表層疊而來并影響到當(dāng)前元素的樣式信息。
??“DOM2 級樣式”增強(qiáng)了 document.defaultView粉私,提供了getComputedStyle() 方法顽腾。
??這個方法接受兩個參數(shù):要取得計算樣式的元素和一個偽元素字符串(例如":after")。如果不需要偽元素信息诺核,第二個參數(shù)可以是 null抄肖。
??getComputedStyle()方法返回一個 CSSStyleDeclaration 對象(與 style 屬性的類型相同),其中包含當(dāng)前元素的所有計算的樣式窖杀。以下面這個 HTML 頁面為例漓摩。
<!DOCTYPE html>
<html>
<head>
<title>Computed Styles Example</title>
<style type="text/css">
#myDiv {
background-color: blue;
width: 100px;
height: 200px;
}
</style>
</head>
<body>
<div id="myDiv" style="background-color: red; border: 1px solid black"></div>
</body>
</html>
??應(yīng)用給這個例子中<div>元素的樣式一方面來自嵌入式樣式表(<style>元素中的樣式),另一方面來自其 style 特性入客。但是管毙,style 特性中設(shè)置了 backgroundColor 和 border,沒有設(shè)置 width 和 height桌硫,后者是通過樣式表規(guī)則應(yīng)用的夭咬。以下代碼可以取得這個元素計算后的樣式。
var myDiv = document.getElementById("myDiv");
var computedStyle = document.defaultView.getComputedStyle(myDiv, null);
console.log(computedStyle.backgroundColor); // "red"
console.log(computedStyle.width); // "100px"
console.log(computedStyle.height); // "200px"
console.log(computedStyle.border); // 在某些瀏覽器中是"1px solid black"
??在這個元素計算后的樣式中铆隘,背景顏色的值是"red"卓舵,寬度值是"100px",高度值是"200px"膀钠。
??我們注意到掏湾,背景顏色不是"blue",因為這個樣式在自身的 style 特性中已經(jīng)被覆蓋了托修。邊框?qū)傩钥赡軙部赡懿粫祷貥邮奖碇袑嶋H的 border 規(guī)則(Opera 會返回忘巧,但其他瀏覽器不會)恒界。
??存在這個差別的原因是不同瀏覽器解釋綜合(rollup)屬性(如 border)的方式不同睦刃,因為設(shè)置這種屬性實際上會涉及很多其他屬性。在設(shè)置 border 時十酣,實際上是設(shè)置了四個邊的邊框?qū)挾壬尽㈩伾食ぁ邮綄傩裕?border-left-width 、 border-top-color 兴泥、 border-bottom-style 工育, 等 等 )。
??因 此 搓彻, 即 使 computedStyle.border 不會在所有瀏覽器中都返回值如绸,但 computedStyle.borderLeftWidth 會返回值。
??需要注意的是旭贬,即使有些瀏覽器支持這種功能怔接,但表示值的方式可能會有所區(qū)別。例如稀轨,F(xiàn)irefox 和 Safari 會將所有顏色轉(zhuǎn)換成 RGB 格式(例如紅色是 rgb(255,0,0))扼脐。
??因此,在使用 getComputedStyle()方法時奋刽,最好多在幾種瀏覽器中測試一下瓦侮。
??IE 不支持 getComputedStyle() 方法,但它有一種類似的概念佣谐。在 IE 中肚吏,每個具有 style 屬性的元素還有一個 currentStyle 屬性。這個屬性是CSSStyleDeclaration 的實例台谍,包含當(dāng)前元素全部計算后的樣式须喂。取得這些樣式的方式也差不多,如下面的例子所示趁蕊。
var myDiv = document.getElementById("myDiv");
var computedStyle = myDiv.currentStyle;
console.log(computedStyle.backgroundColor); // "red"
console.log(computedStyle.width); // "100px"
console.log(computedStyle.height); // "200px"
console.log(computedStyle.border); // undefined
??與 DOM 版本的方式一樣坞生,IE 也沒有返回 border 樣式,因為這是一個綜合屬性掷伙。
??無論在哪個瀏覽器中是己,最重要的一條是要記住所有計算的樣式都是只讀的;不能修改計算后樣式對象中的 CSS 屬性任柜。
??此外卒废,計算后的樣式也包含屬于瀏覽器內(nèi)部樣式表的樣式信息,因此任何具有默認(rèn)值的 CSS 屬性都會表現(xiàn)在計算后的樣式中宙地。
??例如摔认,所有瀏覽器中的 visibility 屬性都有一個默認(rèn)值,但這個值會因?qū)崿F(xiàn)而異宅粥。在默認(rèn)情況下参袱,有的瀏覽器將 visibility 屬性設(shè)置為"visible",而有的瀏覽器則將其設(shè)置為"inherit"。換句話說抹蚀,不能指望某個 CSS 屬性的默認(rèn)值在不同瀏覽器中是相同的剿牺。如果你需要元素具有某個特定的默認(rèn)值,應(yīng)該手工在樣式表中指定該值环壤。
2.2晒来、操作樣式表
??CSSStyleSheet 類型表示的是樣式表,包括通過 <link> 元素包含的樣式表和在<style>元素中定義的樣式表郑现。
??這兩個元素本身分別是由 HTMLLinkElement 和 HTMLStyleElement 類型表示的湃崩。但是,CSSStyleSheet 類型相對更加通用一些接箫,它只表示樣式表竹习,而不管這些樣式表在 HTML 中是如何定義的。
??此外列牺,上述兩個針對元素的類型允許修改 HTML 特性整陌,但 CSSStyleSheet 對象則是一套只讀的接口(有一個屬性例外)。使用下面的代碼可以確定瀏覽器是否支持 DOM2 級樣式表瞎领。
var supportsDOM2StyleSheets =
document.implementation.hasFeature("StyleSheets", "2.0");
??CSSStyleSheet 繼承自 StyleSheet泌辫,后者可以作為一個基礎(chǔ)接口來定義非 CSS 樣式表。從StyleSheet 接口繼承而來的屬性如下九默。
- disabled:表示樣式表是否被禁用的布爾值震放。這個屬性是可讀/寫的,將這個值設(shè)置為 true 可以禁用樣式表驼修。
- href:如果樣式表是通過<link>包含的殿遂,則是樣式表的 URL;否則乙各,是 null墨礁。
- media:當(dāng)前樣式表支持的所有媒體類型的集合。與所有 DOM 集合一樣耳峦,這個集合也有一個 length 屬性和一個 item() 方法恩静。也可以使用方括號語法取得集合中特定的項。如果集合是空列表蹲坷,表示樣式表適用于所有媒體驶乾。在 IE 中,media 是一個反映<link>和<style>元素 media 特性值的字符串循签。
- ownerNode:指向擁有當(dāng)前樣式表的節(jié)點的指針级乐,樣式表可能是在 HTML 中通過<link>或<style/>引入的(在 XML 中可能是通過處理指令引入的)。如果當(dāng)前樣式表是其他樣式表通過 @import 導(dǎo)入的县匠,則這個屬性值為 null风科。IE 不支持這個屬性罕扎。
- parentStyleSheet:在當(dāng)前樣式表是通過 @import 導(dǎo)入的情況下,這個屬性是一個指向?qū)胨臉邮奖淼闹羔槨?/li>
- title:ownerNode 中 title 屬性的值丐重。
- type:表示樣式表類型的字符串。對 CSS 樣式表而言杆查,這個字符是"type/css"扮惦。
??除 了 disabled 屬性之外,其他屬性都是只讀的亲桦。在支持以上所有這些屬性的基礎(chǔ)上崖蜜,CSSStyleSheet 類型還支持下列屬性和方法:
- cssRules:樣式表中包含的樣式規(guī)則的集合。IE 不支持這個屬性客峭,但有一個類似的 rules 屬性豫领。
- ownerRule:如果樣式表是通過@import 導(dǎo)入的,這個屬性就是一個指針舔琅,指向表示導(dǎo)入的規(guī)則等恐;否則,值為 null备蚓。IE 不支持這個屬性课蔬。
- deleteRule(index):刪除 cssRules 集合中指定位置的規(guī)則。IE 不支持這個方法郊尝,但支持一個類似的 removeRule() 方法二跋。
- insertRule(rule, index):向 cssRules 集合中指定的位置插入 rule 字符串。IE 不支持這個方法流昏,但支持一個類似的 addRule() 方法扎即。
??應(yīng)用于文檔的所有樣式表是通過 document.styleSheets 集合來表示的。通過這個集合的 length 屬性可以獲知文檔中樣式表的數(shù)量况凉,而通過方括號語法或 item() 方法可以訪問每一個樣式表谚鄙。示例:
var sheet = null;
for (var i=0, len=document.styleSheets.length; i < len; i++){
sheet = document.styleSheets[i];
console.log(sheet.href);
}
??以上代碼可以輸出文檔中使用的每一個樣式表的 href 屬性(<style>元素包含的樣式表沒有 href 屬性)。
??不同瀏覽器的 document.styleSheets 返回的樣式表也不同刁绒。所有瀏覽器都會包含<style>元素和 rel 特性被設(shè)置為"stylesheet"的<link>元素引入的樣式表襟锐。
??IE 和 Opera 也包含 rel 特性被設(shè)置為"alternate stylesheet"的<link>元素引入的樣式表。
??也可以直接通過<link>或<style>元素取得 CSSStyleSheet 對象膛锭。DOM 規(guī)定了一個包含 CSSStyleSheet 對象的屬性粮坞,名叫 sheet;除了 IE初狰,其他瀏覽器都支持這個屬性莫杈。IE 支持的是 styleSheet 屬性。要想在不同瀏覽器中都能取得樣式表對象奢入,可以使用下列代碼筝闹。
function getStyleSheet(element){
return element.sheet || element.styleSheet;
}
// 取得第一個<link/>元素引入的樣式表
var link = document.getElementsByTagName("link")[0];
var sheet = getStylesheet(link);
??這里的 getStyleSheet() 返回的樣式表對象與 document.styleSheets 集合中的樣式表對象相同媳叨。
1. CSS 規(guī)則
??CSSRule 對象表示樣式表中的每一條規(guī)則。實際上关顷,CSSRule 是一個供其他多種類型繼承的基類型糊秆,其中最常見的就是 CSSStyleRule 類型,表示樣式信息(其他規(guī)則還有 @import议双、@font-face痘番、@page 和 @charset,但這些規(guī)則很少有必要通過腳本來訪問)平痰。
??CSSStyleRule 對象還包含下列屬性汞舱。
- cssText:返回整條規(guī)則對應(yīng)的文本。由于瀏覽器對樣式表的內(nèi)部處理方式不同宗雇,返回的文本可能會與樣式表中實際的文本不一樣昂芜;Safari 始終都會將文本轉(zhuǎn)換成全部小寫。IE 不支持這個屬性赔蒲。
- parentRule:如果當(dāng)前規(guī)則是導(dǎo)入的規(guī)則泌神,這個屬性引用的就是導(dǎo)入規(guī)則;否則舞虱,這個值為 null腻扇。IE 不支持這個屬性。
- parentStyleSheet:當(dāng)前規(guī)則所屬的樣式表砾嫉。IE 不支持這個屬性幼苛。
- selectorText:返回當(dāng)前規(guī)則的選擇符文本。由于瀏覽器對樣式表的內(nèi)部處理方式不同焕刮,返回的文本可能會與樣式表中實際的文本不一樣(例如:Safari 3 之前的版本始終會將文本轉(zhuǎn)換成全部小寫)舶沿。在 Firefox、Safari配并、Chrome 和 IE 中這個屬性是只讀的括荡。Opera 允許修改 selectorText。
- style:一個 CSSStyleDeclaration 對象溉旋,可以通過它設(shè)置和取得規(guī)則中特定的樣式值畸冲。
- type:表示規(guī)則類型的常量值。對于樣式規(guī)則观腊,這個值是 1邑闲。IE 不支持這個屬性。
??其中三個最常用的屬性是 cssText梧油、selectorText 和 style苫耸。cssText 屬性與 style.cssText 屬性類似,但并不相同儡陨。前者包含選擇符文本和圍繞樣式信息的花括號褪子,后者指包含樣式信息(類似于元素的 style.cssText)量淌。此外,cssText 是只讀的嫌褪,而 style.cssText 也可以被重寫呀枢。
??大多數(shù)情況下,僅使用 style 屬性就可以滿足所有操作樣式規(guī)則的需求了笼痛。這個對象就像每個元素上的 style 屬性一樣裙秋,可以通過它讀取和修改規(guī)則中的樣式信息。以下面的 CSS 規(guī)則為例晃痴。
div.box {
background-color: blue;
width: 100px;
height: 200px;
}
??假設(shè)這條規(guī)則位于頁面中的第一個樣式表中,而且這個樣式表中只有這一條樣式規(guī)則财忽,那么通過下列代碼可以取得這條規(guī)則的各種信息倘核。
var sheet = document.styleSheets[0];
var rules = sheet.cssRules || sheet.rules; // 取得規(guī)則列表
var rule = rules[0]; // 取得第一條規(guī)則
console.log(rule.selectorText); // "div.box"
console.log(rule.style.cssText); // 完整的 CSS 代碼
console.log(rule.style.backgroundColor); // "blue"
console.log(rule.style.width); // "100px"
console.log(rule.style.height); // "200px"
??使用這種方式,可以像確定元素的行內(nèi)樣式信息一樣即彪,確定與規(guī)則相關(guān)的樣式信息紧唱。與使用元素的方式一樣,在這種方式下也可以修改樣式信息隶校,如下面的例子所示漏益。
var sheet = document.styleSheets[0];
var rules = sheet.cssRules || sheet.rules; // 取得規(guī)則列表
var rule = rules[0]; // 取得第一條規(guī)則
rule.style.backgroundColor = "red"
??必須要注意的是,以這種方式修改規(guī)則會影響頁面中適用于該規(guī)則的所有元素深胳。換句話說绰疤,如果有兩個帶有 box 類的<div>元素,那么這兩個元素都會應(yīng)用修改后的樣式舞终。
2. 創(chuàng)建規(guī)則
??DOM 規(guī)定轻庆,要向現(xiàn)有樣式表中添加新規(guī)則,需要使用 insertRule() 方法敛劝。這個方法接受兩個參數(shù):規(guī)則文本和表示在哪里插入規(guī)則的索引余爆。示例:
sheet.insertRule("body { background-color: silver }", 0); // DOM 方法
??上述例子插入的規(guī)則會改變元素的背景顏色。插入的規(guī)則將成為樣式表中的第一條規(guī)則(插入到了位置 0)—— 規(guī)則的次序在確定層疊之后應(yīng)用到文檔的規(guī)則時至關(guān)重要夸盟。Firefox蛾方、Safari、Opera 和 Chrome 都支持 insertRule() 方法上陕。
??IE8 及更早版本支持一個類似的方法桩砰,名叫 addRule(),也接收兩必選參數(shù):選擇符文本和 CSS 樣式信息释簿;一個可選參數(shù):插入規(guī)則的位置五芝。在 IE 中插入與前面例子相同的規(guī)則,可使用如下代碼辕万。
sheet.addRule("body", "background-color: silver", 0); // 僅對 IE 有效
??有關(guān)這個方法的規(guī)定中說枢步,最多可以使用 addRule() 添加 4 095 條樣式規(guī)則沉删。超出這個上限的調(diào)用將會導(dǎo)致錯誤。
??要以跨瀏覽器的方式向樣式表中插入規(guī)則醉途,可以使用下面的函數(shù)矾瑰。這個函數(shù)接受 4 個參數(shù):要向其中添加規(guī)則的樣式表以及與 addRule() 相同的 3 個參數(shù),如下所示隘擎。
function insertRule(sheet, selectorText, cssText, position){
if (sheet.insertRule){
sheet.insertRule(selectorText + "{" + cssText + "}", position);
} else if (sheet.addRule){
sheet.addRule(selectorText, cssText, position);
}
}
??下面是調(diào)用這個函數(shù)的示例代碼殴穴。
insertRule(document.styleSheets[0], "body", "background-color: silver", 0);
??雖然可以像這樣來添加規(guī)則,但隨著要添加規(guī)則的增多货葬,這種方法就會變得非常繁瑣采幌。因此,如果要添加的規(guī)則非常多震桶,我們建議還是采用動態(tài)加載樣式表的技術(shù)休傍。
3. 刪除規(guī)則
??從樣式表中刪除規(guī)則的方法是 deleteRule()锹淌,這個方法接受一個參數(shù):要刪除的規(guī)則的位置凭疮。
??例如舰绘,要刪除樣式表中的第一條規(guī)則穿撮,可以使用以下代碼达传。
sheet.deleteRule(0); // DOM 方法
??IE 支持的類似方法叫 removeRule()摹恨,使用方法相同铃岔,如下所示:
sheet.removeRule(0); // 僅對 IE 有效
??下面是一個能夠跨瀏覽器刪除規(guī)則的函數(shù)牲距。第一個參數(shù)是要操作的樣式表江咳,第二個參數(shù)是要刪除的規(guī)則的索引逢净。
function deleteRule(sheet, index){
if (sheet.deleteRule){
sheet.deleteRule(index);
} else if (sheet.removeRule){
sheet.removeRule(index);
}
}
??調(diào)用這個函數(shù)的方式如下。
deleteRule(document.styleSheets[0], 0);
??與添加規(guī)則相似歼指,刪除規(guī)則也不是實際 Web 開發(fā)中常見的做法汹胃。考慮到刪除規(guī)則可能會影響 CSS 層疊的效果东臀,因此請大家慎重使用着饥。
2.3、元素大小
??本節(jié)介紹的屬性和方法并不屬于“DOM2級樣式”規(guī)范惰赋,但卻與 HTML 元素的樣式息息相關(guān)宰掉。DOM 中沒有規(guī)定如何確定頁面中元素的大小。IE 為此率先引入了一些屬性赁濒,以便開發(fā)人員使用轨奄。目前,所有主要的瀏覽器都已經(jīng)支持這些屬性拒炎。
1. 偏移量
??首先要介紹的屬性涉及偏移量(offset dimension)挪拟,包括元素在屏幕上占用的所有可見的空間。元素的可見大小尤其高度击你、寬度決定玉组,包括所有內(nèi)邊距谎柄、滾動條和邊框大小(注意惯雳,不包括外邊距)朝巫。通過下列 4 個屬性可以取得元素的偏移量。
- offsetHeight:元素在垂直方向上占用的空間大小石景,以像素計劈猿。包括元素的高度、(可見的)水平滾動條的高度潮孽、上邊框高度和下邊框高度揪荣。
- offsetWidth:元素在水平方向上占用的空間大小,以像素計往史。包括元素的高度仗颈、(可見的)垂直滾動條的寬度、左邊框?qū)挾群陀羞吙驅(qū)挾取?/li>
- offsetLeft:元素的左外邊框至包含元素的左內(nèi)邊框之間的像素距離怠堪。
- offsetTop:元素的上外邊框至包含元素的上內(nèi)邊框之間的像素距離揽乱。
??其中名眉,offsetLeft 和 offsetTop 屬性與包含元素有關(guān)粟矿,包含元素的引用保存在 offsetParent 屬性中。offsetParent 屬性不一定與 parentNode 的值相等损拢。
??例如陌粹,<td>元素的 offsetParent 是作為其祖先元素的<table>元素,因為<table>是在 DOM 層次中距<td>最近的一個具有大小的元素福压。
??下圖形象地展示了上面幾個屬性表示的不同大小掏秩。
??要想知道某個元素在頁面上的偏移量,將這個元素的 offsetLeft 和 offsetTop 與其 offsetParent 的相同屬性相加荆姆,如此循環(huán)直至根元素蒙幻,就可以得到一個基本準(zhǔn)確的值。以下兩個函數(shù)就可以用于分別取得元素的左和上偏移量胆筒。
// 元素的左偏移量
function getElementLeft(element){
var actualLeft = element.offsetLeft;
var current = element.offsetParent;
while (current !== null){
actualLeft += current.offsetLeft;
current = current.offsetParent;
}
return actualLeft;
}
// 元素的上偏移量
function getElementTop(element){
var actualTop = element.offsetTop;
var current = element.offsetParent;
while (current !== null){
actualTop += current. offsetTop;
current = current.offsetParent;
}
return actualTop;
}
??這兩個函數(shù)利用 offsetParent 屬性在 DOM 層次中逐級向上回溯邮破,將每個層次中的偏移量屬性合計到一塊。對于簡單的 CSS 布局的頁面仆救,這兩函數(shù)可以得到非常精確的結(jié)果抒和。對于使用表格和內(nèi)嵌框架布局的頁面,由于不同瀏覽器實現(xiàn)這些元素的方式不同彤蔽,因此得到的值就不太精確了摧莽。
??一般來說,頁面中的所有元素都會被包含在幾個<div>元素中顿痪,而這些<div>元素的 offsetParent 又是 <body>元素镊辕,所以 getElementLeft() 與 getElementTop() 會返回與 offsetLeft 和 offsetTop 相同的值油够。
??所有這些偏移量屬性都是只讀的,而且每次訪問它們都需要重新計算丑蛤。因此叠聋,應(yīng)該盡量避免重復(fù)訪問這些屬性;如果需要重復(fù)使用其中某些屬性的值受裹,可以將它們保存在局部變量中碌补,以提高性能。
2. 客戶區(qū)大小
??元素的客戶區(qū)大忻奕摹(client dimension)厦章,指的是元素內(nèi)容及其內(nèi)邊距所占據(jù)的空間大小。有關(guān)客戶區(qū)大小的屬性有兩個:clientWidth 和 clientHeight照藻。
??其中袜啃,clientWidth 屬性是元素內(nèi)容區(qū)寬度加上左右內(nèi)邊距寬度;clientHeight 屬性是元素內(nèi)容區(qū)高度加上上下內(nèi)邊距高度幸缕。
??下圖形象地說明了這些屬性表示的大小群发。
??從字面上看,客戶區(qū)大小就是元素內(nèi)部的空間大小发乔,因此滾動條占用的空間不計算在內(nèi)熟妓。最常用到這些屬性的情況,就是像確定瀏覽器視口大小的時候栏尚。
??如下面的例子所示起愈,要確定瀏覽器視口大小,可以使用document.documentElement 或 document.body(在 IE7 之前的版本中)的 clientWidth 和 clientHeight译仗。
function getViewport(){
if (document.compatMode == "BackCompat"){
return {
width: document.body.clientWidth,
height: document.body.clientHeight
};
} else {
return {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
};
}
}
??這個函數(shù)首先檢查 document.compatMode 屬性抬虽,以確定瀏覽器是否運行在混雜模式。Safari 3.1 之前的版本不支持這個屬性纵菌,因此就會自動執(zhí)行 else 語句阐污。Chrome、Opera 和 Firefox 大多數(shù)情況下都運行在標(biāo)準(zhǔn)模式下咱圆,因此它們也會前進(jìn)到 else 語句笛辟。
??這個函數(shù)會返回一個對象,包含兩個屬性:width 和 height闷堡;表示瀏覽器視口(<html>或<body>元素)的大小隘膘。
與偏移量相似,客戶區(qū)大小也是只讀的杠览,也是每次訪問都要重新計算的弯菊。
3. 滾動大小
??最后要介紹的是滾動大小(scroll dimension),指的是包含滾動內(nèi)容的元素的大小管钳。有些元素(例如<html>元素)钦铁,即使沒有執(zhí)行任何代碼也能自動地添加滾動條;但另外一些元素才漆,則需要通過 CSS 的 overflow 屬性進(jìn)行設(shè)置才能滾動牛曹。以下是 4 個與滾動大小相關(guān)的屬性。
- scrollHeight:在沒有滾動條的情況下醇滥,元素內(nèi)容的總高度黎比。
- scrollWidth:在沒有滾動條的情況下,元素內(nèi)容的總寬度鸳玩。
- scrollLeft:被隱藏在內(nèi)容區(qū)域左側(cè)的像素數(shù)阅虫。通過設(shè)置這個屬性可以改變元素的滾動位置。
- scrollTop:被隱藏在內(nèi)容區(qū)域上方的像素數(shù)不跟。通過設(shè)置這個屬性可以改變元素的滾動位置颓帝。
??下圖展示了這些屬性代表的大小。
??scrollWidth 和 scrollHeight 主要用于確定元素內(nèi)容的實際大小窝革。例如购城,通常認(rèn)為<html>元素是在 Web 瀏覽器的視口中滾動的元素(IE6 之前版本運行在混雜模式下時是<body>元素)。因此虐译,帶有垂直滾動條的頁面總高度就是 document.documentElement.scrollHeight瘪板。
??對于不包含滾動條的頁面而言, scrollWidth 和 scrollHeight 與 clientWidth 和 clientHeight 之間的關(guān)系并不十分清晰菱蔬。在這種情況下篷帅,基于 document.documentElement 查看這些屬性會在不同瀏覽器間發(fā)現(xiàn)一些不一致性問題史侣,如下所述拴泌。
- Firefox 中這兩組屬性始終都是相等的,但大小代表的是文檔內(nèi)容區(qū)域的實際尺寸惊橱,而非視口的尺寸蚪腐。
- Opera、Safari 3.1 及更高版本税朴、Chrome 中的這兩組屬性是有差別的回季,其中 scrollWidth 和 scrollHeight 等于視口大小,而 clientWidth 和 clientHeight 等于文檔內(nèi)容區(qū)域的大小正林。
- IE(在標(biāo)準(zhǔn)模式)中的這兩組屬性不相等泡一,其中 scrollWidth 和 scrollHeight 等于文檔內(nèi)容區(qū)域的大小,而 clientWidth 和 clientHeight 等于視口大小觅廓。
??在確定文檔的總高度時(包括基于視口的最小高度時)鼻忠,必須取得 scrollWidth/clientWidth 和 scrollHeight/clientHeight 中的最大值,才能保證在跨瀏覽器的環(huán)境下得到精確的結(jié)果杈绸。下面就是這樣一個例子帖蔓。
var docHeight = Math.max(document.documentElement.scrollHeight,
document.documentElement.clientHeight);
var docWidth = Math.max(document.documentElement.scrollWidth,
document.documentElement.clientWidth);
??注意矮瘟,對于運行在混雜模式下的 IE,則需要用 document.body 代替 document.documentElement塑娇。
??通過 scrollLeft 和 scrollTop 屬性既可以確定元素當(dāng)前滾動的狀態(tài)澈侠,也可以設(shè)置元素的滾動位置。
??在元素尚未被滾動時埋酬,這兩個屬性的值都等于 0哨啃。
??如果元素被垂直滾動了,那么 scrollTop 的值會大于 0写妥,且表示元素上方不可見內(nèi)容的像素高度棘催。
??如果元素被水平滾動了,那么 scrollLeft 的值會大于 0耳标,且表示元素左側(cè)不可見內(nèi)容的像素寬度醇坝。
??這兩個屬性都是可以設(shè)置的,因此將元素的 scrollLeft 和 scrollTop 設(shè)置為 0次坡,就可以重置元素的滾動位置呼猪。下面這個函數(shù)會檢測元素是否位于頂部,如果不是就將其回滾到頂部砸琅。
function scrollToTop(element){
if (element.scrollTop != 0){
element.scrollTop = 0;
}
}
??這個函數(shù)既取得了 scrollTop 的值宋距,也設(shè)置了它的值。
4. 確定元素大小
??IE症脂、Firefox 3+谚赎、Safari 4+、Opera 9.5 及 Chrome 為每個元素都提供了一個 getBoundingClientRect() 方法诱篷。這個方法返回會一個矩形對象壶唤,包含 4 個屬性:left、top棕所、right 和 bottom闸盔。
??這些屬性給出了元素在頁面中相對于視口的位置。但是琳省,瀏覽器的實現(xiàn)稍有不同迎吵。IE8 及更早版本認(rèn)為文檔的左上角坐標(biāo)是 (2, 2),而其他瀏覽器包括 IE9 則將傳統(tǒng)的 (0, 0) 作為起點坐標(biāo)针贬。因此击费,就需要在一開始檢查一下位于 (0, 0) 處的元素的位置,在 IE8 及更早版本中桦他,會返回 (2, 2)蔫巩,而在其他瀏覽器中會返回 (0, 0)。來看下面的函數(shù):
function getBoundingClientRect(element){
if (typeof arguments.callee.offset != "number"){
var scrollTop = document.documentElement.scrollTop;
var temp = document.createElement("div");
temp.style.cssText = "position:absolute;left:0;top:0;";
document.body.appendChild(temp);
arguments.callee.offset = -temp.getBoundingClientRect().top - scrollTop;
document.body.removeChild(temp);
temp = null;
}
var rect = element.getBoundingClientRect();
var offset = arguments.callee.offset;
return {
left: rect.left + offset,
right: rect.right + offset,
top: rect.top + offset,
bottom: rect.bottom + offset
};
}
??上述函數(shù)使用了它自身的屬性來確定是否要對坐標(biāo)進(jìn)行調(diào)整。
??第一步是檢測屬性是否有定義批幌,如果沒有就定義一個础锐。最終的 offset 會被設(shè)置為新元素上坐標(biāo)的負(fù)值,實際上就是在 IE 中設(shè)置為 -2荧缘,在 Firefox 和 Opera 中設(shè)置為 -0皆警。為此,需要創(chuàng)建一個臨時的元素截粗,將其位置設(shè)置在(0,0)信姓,然后再調(diào)用其 getBoundingClientRect()。而之所以要減去視口的 scrollTop绸罗,是為了防止調(diào)用這個函數(shù)時窗口被滾動了意推。這樣編寫代碼,就無需每次調(diào)用這個函數(shù)都執(zhí)行兩次getBoundingClientRect() 了珊蟀。接下來菊值,再在傳入的元素上調(diào)用這個方法并基于新的計算公式創(chuàng)建一個對象。
??對于不支持 getBoundingClientRect() 的瀏覽器育灸,可以通過其他手段取得相同的信息腻窒。一般來說,right 和 left 的差值與 offsetWidth 的值相等磅崭,而 bottom 和 top 的差值與 offsetHeight 相等儿子。而且,left 和 top 屬性大致等于使用本章前面定義的 getElementLeft() 和 getElementTop() 函數(shù)取得的值砸喻。綜合上述柔逼,就可以創(chuàng)建出下面這個跨瀏覽器的函數(shù):
function getBoundingClientRect(element){
var scrollTop = document.documentElement.scrollTop;
var scrollLeft = document.documentElement.scrollLeft;
if (element.getBoundingClientRect){
if (typeof arguments.callee.offset != "number"){
var temp = document.createElement("div");
temp.style.cssText = "position:absolute;left:0;top:0;";
document.body.appendChild(temp);
arguments.callee.offset = -temp.getBoundingClientRect().top - scrollTop;
document.body.removeChild(temp);
temp = null;
}
var rect = element.getBoundingClientRect();
var offset = arguments.callee.offset;
return {
left: rect.left + offset,
right: rect.right + offset,
top: rect.top + offset,
bottom: rect.bottom + offset
};
} else {
var actualLeft = getElementLeft(element);
var actualTop = getElementTop(element);
return {
left: actualLeft - scrollLeft,
right: actualLeft + element.offsetWidth - scrollLeft,
top: actualTop - scrollTop,
bottom: actualTop + element.offsetHeight - scrollTop
}
}
}
??這個函數(shù)在 getBoundingClientRect() 有效時,就是用這個原生方法割岛,而在這個方法無效時則使用默認(rèn)的計算公式愉适。在某些情況下,這個函數(shù)返回的值可能會有所不同蜂桶,例如使用表格布局或使用滾動元素的情況下儡毕。
由于這里使用了 arguments.callee也切,所以這個方法不能在嚴(yán)格模式下使用扑媚。
3、遍歷
??“DOM 2 級遍歷和范圍”模塊定義了兩個用于輔助完成順序遍歷 DOM 結(jié)構(gòu)的類型:NodeIterator 和 TreeWalker雷恃。這兩個類型能夠基于給定的起點對 DOM 結(jié)構(gòu)執(zhí)行深度優(yōu)先(depth-first)的遍歷操作疆股。
??在與 DOM 兼容的瀏覽器中(Firefox 1 及更高版本、Safari 1.3 及更高版本倒槐、Opera 7.6 及更高版本旬痹、Chrome 0.2 及更高版本),都可以訪問到這些類型的對象。IE 不支持 DOM 遍歷两残。使用下列代碼可以檢測瀏覽器對 DOM2 級遍歷能力的支持情況永毅。
var supportsTraversals = document.implementation.hasFeature("Traversal", "2.0");
var supportsNodeIterator = (typeof document.createNodeIterator == "function");
var supportsTreeWalker = (typeof document.createTreeWalker == "function");
??如前所述,DOM 遍歷時深度優(yōu)先的 DOM 結(jié)構(gòu)遍歷人弓,也就是說沼死,移動的方向至少有兩個(取決于使用的遍歷類型)。遍歷以給定節(jié)點為根崔赌,不可能向上超出 DOM 樹的根節(jié)點意蛀。以下面的 HTML 頁面為例:
<!DOCTYPE html>
<html>
<head>
<title>Example</title>
</head>
<body>
<p><b>Hello</b> world!</p>
</body>
</html>
??下圖展示了上述頁面的 DOM 樹。
??任何節(jié)點都可以作為遍歷的根節(jié)點健芭。如果假設(shè)<body>元素為根節(jié)點县钥,那么遍歷的第一步就是訪問<p>元素,然后再訪問同位<body>元素后代的兩個文本節(jié)點慈迈。不過若贮,這次遍歷永遠(yuǎn)不會到達(dá)<html>、<head>元素痒留,也不會到達(dá)不屬于<body>元素子樹的任何節(jié)點兜看。
??而以 document 為根節(jié)點的遍歷則可以訪問到文檔中的全部節(jié)點。下圖展示了對以 document 為根節(jié)點的 DOM 樹進(jìn)行深度優(yōu)先遍歷的先后順序狭瞎。
??從 document 開始依序向前细移,訪問的第一個節(jié)點是 document,訪問的最后一個節(jié)點是包含“world!”的文本節(jié)點熊锭。從文檔最后的文本節(jié)點開始弧轧,遍歷可以反向移動到 DOM 樹的頂端。此時碗殷,訪問的第一個節(jié)點是包含“hello”的文本節(jié)點精绎,訪問的最后一個節(jié)點是 document 節(jié)點。NodeIterator 和 TreeWalker 都以這種方式執(zhí)行遍歷锌妻。
NodeIterator
??NodeIterator 類型是兩者中比較簡單的一個代乃,可以使用document.createNodeIterator() 方法創(chuàng)建它的新實例。這個方法接受下列 4 個參數(shù)仿粹。
- root:想要作為搜索起點的樹中的節(jié)點搁吓。
- whatToShow:表示要訪問哪些節(jié)點的數(shù)字代碼。
- filter:是一個 NodeFilter 對象吭历,或者一個表示應(yīng)該接受還是拒絕某種特定節(jié)點的函數(shù)堕仔。
- entityReferenceExpansion:布爾值,表示是否要擴(kuò)展實體引用晌区。這個參數(shù)在 HTML 頁面中沒有用摩骨,因為其中的實體引用不能擴(kuò)展通贞。
??whatToShow 參數(shù)是一個位掩碼,通過應(yīng)用一或多個過濾器(filter)來確定要訪問哪些節(jié)點恼五。這個參數(shù)的值以常量形式在 NodeFilter 類型中定義昌罩,如下所示。
- NodeFilter.SHOW_ALL:顯示所有類型的節(jié)點灾馒。
- NodeFilter.SHOW_ELEMENT:顯示元素節(jié)點峡迷。
- NodeFilter.SHOW_ATTRIBUTE:顯示特性節(jié)點。由于 DOM 結(jié)構(gòu)原因你虹,實際上不能使用這個值绘搞。
- NodeFilter.SHOW_TEXT:顯示文本節(jié)點。
- NodeFilter.SHOW_CDATA_SECTION:顯示 CDATA 節(jié)點傅物。對 HTML 頁面沒有用夯辖。
- NodeFilter.SHOW_ENTITY_REFERENCE:顯示實體引用節(jié)點。對 HTML 頁面沒有用董饰。
- NodeFilter.SHOW_ENTITYE:顯示實體節(jié)點蒿褂。對 HTML 頁面沒有用。
- NodeFilter.SHOW_PROCESSING_INSTRUCTION:顯示處理指令節(jié)點卒暂。對 HTML 頁面沒有用啄栓。
- NodeFilter.SHOW_COMMENT:顯示注釋節(jié)點。
- NodeFilter.SHOW_DOCUMENT:顯示文檔節(jié)點也祠。
- NodeFilter.SHOW_DOCUMENT_TYPE:顯示文檔類型節(jié)點昙楚。
- NodeFilter.SHOW_DOCUMENT_FRAGMENT:顯示文檔片段節(jié)點。對 HTML 頁面沒有用诈嘿。
- NodeFilter.SHOW_NOTATION:顯示符號節(jié)點堪旧。對 HTML 頁面沒有用。
??除了 NodeFilter.SHOW_ALL 之外奖亚,可以使用按位或操作符來組合多個選項淳梦,如下面的例子所示:
var whatToShow = NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT;
??可以通過 createNodeIterator() 方法的 filter 參數(shù)來指定自定義的 NodeFilter 對象,或者指定一個功能類似節(jié)點過濾器(node filter)的函數(shù)昔字。
??每個 NodeFilter 對象只有一個方法爆袍,即 acceptNode()华糖;如果應(yīng)該訪問給定的節(jié)點缸兔,該方法返回 NodeFilter.FILTER_ACCEPT,如果不應(yīng)該訪問給定的節(jié)點置济,該方法返回 NodeFilter.FILTER_SKIP所坯。
??由于 NodeFilter 是一個抽象的類型谆扎,因此不能直接創(chuàng)建它的實例。在必要時芹助,只要創(chuàng)建一個包含 acceptNode() 方法的對象堂湖,然后將這個對象傳入 createNodeIterator() 中即可。例如状土,下列代碼展示了如何創(chuàng)建一個只顯示<p>元素的節(jié)點迭代器无蜂。
var filter = {
acceptNode: function(node){
return node.tagName.toLowerCase() == "p" ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
}
};
var iterator = document.createNodeIterator(root, NodeFilter.SHOW_ELEMENT, filter, false);
??第三個參數(shù)也可以是一個與 acceptNode() 方法類似的函數(shù),如下所示蒙谓。
var filter = function(node){
return node.tagName.toLowerCase() == "p" ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
};
var iterator = document.createNodeIterator(root, NodeFilter.SHOW_ELEMENT, filter, false);
??一般來說斥季,這就是在 JavaScript 中使用這個方法的形式,這種形式比較簡單累驮,而且也跟其他的 JavaScript 代碼很相似酣倾。如果不指定過濾器,那么應(yīng)該在第三個參數(shù)的位置上傳入 null谤专。
??下面的代碼創(chuàng)建了一個能夠訪問所有類型節(jié)點的簡單的 NodeIterator躁锡。
var iterator = document.createNodeIterator(document, NodeFilter.SHOW_ALL, null, false);
??NodeIterator 類型的兩個主要方法是 nextNode() 和 previousNode()。顧名思義置侍,在深度優(yōu)先的 DOM 子樹遍歷中映之,nextNode() 方法用于向前前進(jìn)一步,而 previousNode() 用于向后后退一步蜡坊。
??在剛剛創(chuàng)建的 NodeIterator 對象中杠输,有一個內(nèi)部指針指向根節(jié)點,因此第一次調(diào)用 nextNode() 會返回根節(jié)點秕衙。當(dāng)遍歷到 DOM 子樹的最后一個節(jié)點時蠢甲,nextNode() 返回 null。
??previousNode() 方法的工作機(jī)制類似据忘。當(dāng)遍歷到 DOM 子樹的最后一個節(jié)點峡钓,且 previousNode() 返回根節(jié)點之后,再次調(diào)用它就會返回 null若河。以下面的 HTML 片段為例能岩。
<div id="div1">
<p><b>Hello</b> world!</p>
<ul>
<li>List item 1</li>
<li>List item 2</li>
<li>List item 3</li>
</ul>
</div>
??假設(shè)我們想要遍歷<div>元素中的所有元素,那么可以使用下列代碼萧福。
var div = document.getElementById("div1");
var iterator = document.createNodeIterator(div, NodeFilter.SHOW_ELEMENT, null, false);
var node = iterator.nextNode();
while (node !== null) {
console.log(node.tagName); // 輸出標(biāo)簽名
node = iterator.nextNode();
}
??在上述例子中拉鹃,第一次調(diào)用 nextNode() 返回<p>元素。因為在到達(dá) DOM 子樹末端時 nextNode() 返回 null鲫忍,所以這里使用了 while 語句在每次循環(huán)時檢查對 nextNode() 的調(diào)用是否返回了 null膏燕。執(zhí)行上面的代碼會顯示如下標(biāo)簽名:
DIV
P
B
UL
LI
LI
LI
??也許用不著顯示那么多信息,你只想返回遍歷中遇到的<li>元素悟民。很簡單坝辫,只要使用一個過濾器即可,如下面的例子所示射亏。
var div = document.getElementById("div1");
var filter = function(node){
return node.tagName.toLowerCase() == "li" ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
};
var iterator = document.createNodeIterator(div, NodeFilter.SHOW_ELEMENT, filter, false);
var node = iterator.nextNode();
while (node !== null) {
console.log(node.tagName); // 輸出標(biāo)簽名
node = iterator.nextNode();
}
??在上述例子中近忙,迭代器只會返回<li>元素竭业。
??由于 nextNode() 和 previousNode() 方法都基于 NodeIterator 在 DOM 結(jié)構(gòu)中的內(nèi)部指針工作,所以 DOM 結(jié)構(gòu)的變化會反映在遍歷的結(jié)果中及舍。
??Firefox 3.5 之前的版本沒有實現(xiàn) createNodeIterator() 方法未辆,但卻支持下一節(jié)要討論的 createTreeWalker() 方法。
3.2锯玛、TreeWalker
??TreeWalker 是 NodeIterator 的一個更高級的版本咐柜。除了包括 nextNode() 和 previousNode() 在內(nèi)的相同的功能之外,這個類型還提供了下列用于在不同方向上遍歷 DOM 結(jié)構(gòu)的方法攘残。
- parentNode():遍歷到當(dāng)前節(jié)點的父節(jié)點拙友;
- firstChild():遍歷到當(dāng)前節(jié)點的第一個子節(jié)點;
- lastChild():遍歷到當(dāng)前節(jié)點的最后一個子節(jié)點歼郭;
- nextSibling():遍歷到當(dāng)前節(jié)點的下一個同輩節(jié)點遗契;
- previousSibling():遍歷到當(dāng)前節(jié)點的上一個同輩節(jié)點
??創(chuàng)建 TreeWalker 對象要使用 document.createTreeWalker() 方法,這個方法接受的 4 個參數(shù)與 document.createNodeIterator() 方法相同:作為遍歷起點的根節(jié)點实撒、要顯示的節(jié)點類型姊途、過濾器和一個表示是否擴(kuò)展實體引用的布爾值。
??由于這兩個創(chuàng)建方法很相似知态,所以很容易用 TreeWalker 來代替 NodeIterator捷兰,如下面的例子所示。
var div = document.getElementById("div1");
var filter = function(node){
return node.tagName.toLowerCase() == "li" ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
};
var walker= document.createTreeWalker(div, NodeFilter.SHOW_ELEMENT, filter, false);
var node = iterator.nextNode();
while (node !== null) {
console.log(node.tagName); // 輸出標(biāo)簽名
node = iterator.nextNode();
}
??在這里负敏,filter 可以返回的值有所不同贡茅。除了 NodeFilter.FILTER_ACCEPT 和 NodeFilter.FILTER_SKIP 之外,還可以使用 NodeFilter.FILTER_REJECT其做。
??在使用 NodeIterator 對象時顶考,NodeFilter.FILTER_SKIP 與 NodeFilter.FILTER_REJECT 的作用相同:跳過指定的節(jié)點。
??但在使用 TreeWalker 對象時妖泄,NodeFilter.FILTER_SKIP 會跳過相應(yīng)節(jié)點繼續(xù)前進(jìn)到子樹中的下一個節(jié)點驹沿,而 NodeFilter.FILTER_REJECT 則會跳過相應(yīng)節(jié)點及該節(jié)點的整個子樹。
??例如蹈胡,將前面例子中的 NodeFilter.FILTER_SKIP 修改成NodeFilter.FILTER_REJECT渊季,結(jié)果就是不會訪問任何節(jié)點。這是因為第一個返回的節(jié)點是 <div>罚渐,它的標(biāo)簽名不是 "li"却汉,于是就會返回NodeFilter.FILTER_REJECT,
??這意味著遍歷會跳過整個子樹荷并。在這個例子中合砂,<div>元素是遍歷的根節(jié)點,于是結(jié)果就會停止遍歷源织。
??當(dāng)然翩伪,TreeWalker 真正強(qiáng)大的地方在于能夠在 DOM 結(jié)構(gòu)中沿任何方向移動微猖。使用 TreeWalker 遍歷 DOM 樹,即使不定義過濾器幻工,也可以取得所有<li>元素励两,如下面的代碼所示黎茎。
var div = document.getElementById("div1");
var walker = document.createTreeWalker(div, NodeFilter.SHOW_ELEMENT, null, false);
walker.firstChild(); // 轉(zhuǎn)到<p>
walker.nextSibling(); // 轉(zhuǎn)到<ul>
var node = walker.firstChild(); // 轉(zhuǎn)到第一個<li>
while (node !== null) {
console.log(node.tagName);
node = walker.nextSibling();
}
??因為我們知道<li>元素在文檔結(jié)構(gòu)中的位置囊颅,所以可以直接定位到那里,即使用 firstChild() 轉(zhuǎn)到<p>元素傅瞻,使用 nextSibling() 轉(zhuǎn)到<ul>元素踢代,然后再使用 firstChild() 轉(zhuǎn)到第一個<li>元素。
??注意嗅骄,此處 TreeWalker 只返回元素(由傳入到 createTreeWalker() 的第二個參數(shù)決定)胳挎。因此,可以放心地使用 nextSibling() 訪問每一個<li>元素溺森,直至這個方法最后返回 null慕爬。
??TreeWalker 類型還有一個屬性,名叫 currentNode屏积,表示任何遍歷方法在上一次遍歷中返回的節(jié)點医窿。通過設(shè)置這個屬性也可以修改遍歷繼續(xù)進(jìn)行的起點,如下面的例子所示炊林。
var node = walker.nextNode();
console.log(node === walker.currentNode); // true
walker.currentNode = document.body; // 修改起點
??與 NodeIterator 相比姥卢,TreeWalker 類型在遍歷 DOM 時擁有更大的靈活性。由于 IE 中沒有對應(yīng)的類型和方法渣聚,所以使用遍歷的跨瀏覽器解決方案非常少見独榴。
4、 范圍
??為了讓開發(fā)人員更方便地控制頁面奕枝,“DOM2 級遍歷和范圍”模塊定義了“范圍”(range)接口棺榔。
??通過范圍可以選擇文檔中的一個區(qū)域,而不必考慮節(jié)點的界限(選擇在后臺完成隘道,對用戶是不可見的)症歇。
??在常規(guī)的 DOM 操作不能更有效地修改文檔時,使用范圍往往可以達(dá)到目的薄声。Firefox当船、Opera、Safari 和 Chrome 都支持 DOM 范圍默辨。IE 以專有方式實現(xiàn)了自己的范圍特性德频。
4.1、 DOM 中的范圍
??DOM2 級在 Document 類型中定義了 createRange() 方法缩幸。在兼容 DOM 的瀏覽器中壹置,這個方法屬于 document 對象竞思。
??createRange 使用 hasFeature() 或者直接檢測該方法,都可以確定瀏覽器是否支持范圍钞护。
var supportsRange = document.implementation.hasFeature("Range", "2.0");
var alsoSupportsRange = (typeof document.createRange == "function");
??如果瀏覽器支持范圍盖喷,那么就可以使用 createRange() 來創(chuàng)建 DOM 范圍,如下所示:
var range = document.createRange();
??與節(jié)點類似难咕,新創(chuàng)建的范圍也直接與創(chuàng)建它的文檔關(guān)聯(lián)在一起课梳,不能用于其他文檔。創(chuàng)建了范圍之后余佃,接下來就可以使用它在后臺選擇文檔中的特定部分暮刃。而創(chuàng)建范圍并設(shè)置了其位置之后,還可以針對范圍的內(nèi)容執(zhí)行很多種操作爆土,從而實現(xiàn)對底層 DOM 樹的更精細(xì)的控制椭懊。
??每個范圍由一個 Range 類型的實例表示,這個實例擁有很多屬性和方法步势。下列屬性提供了當(dāng)前范圍在文檔中的位置信息氧猬。
- startContainer:包含范圍起點的節(jié)點(即選區(qū)中第一個節(jié)點的父節(jié)點)。
- startOffset:范圍在 startContainer 中起點的偏移量坏瘩。如果 startContainer 是文本節(jié)點盅抚、注釋節(jié)點或 CDATA 節(jié)點,那么 startOffset 就是范圍起點之前跳過的字符數(shù)量桑腮。否則泉哈,startOffset 就是范圍中第一個子節(jié)點的索引。
- endContainer:包含范圍終點的節(jié)點(即選區(qū)中最后一個節(jié)點的父節(jié)點)破讨。
- endOffset:范圍在 endContainer 中終點的偏移量(與 startOffset 遵循相同的取值規(guī)則)丛晦。
- commonAncestorContainer:startContainer 和 endContainer 共同的祖先節(jié)點在文檔樹中位置最深的那個。
??在把范圍放到文檔中特定的位置時提陶,這些屬性都會被賦值烫沙。
1. 用 DOM 范圍實現(xiàn)簡單選擇
??要使用范圍來選擇文檔中的一部分,最簡的方式就是使用 selectNode() 或 selectNodeContents()隙笆。這兩個方法都接受一個參數(shù)锌蓄,即一個 DOM 節(jié)點,然后使用該節(jié)點中的信息來填充范圍撑柔。
??其中瘸爽,selectNode() 方法選擇整個節(jié)點,包括其子節(jié)點铅忿;而 selectNodeContents() 方法則只選擇節(jié)點的子節(jié)點剪决。以下面的 HTML 代碼為例。
<!DOCTYPE html>
<html>
<body>
<p id="p1"><b>Hello</b> world!</p>
</body>
</html>
??我們可以使用下列代碼來創(chuàng)建范圍:
var range1 = document.createRange(),
range2 = document.createRange(),
p1 = document.getElementById("p1");
range1.selectNode(p1);
range2.selectNodeContents(p1);
??這里創(chuàng)建的兩個范圍包含文檔中不同的部分:rang1 包含<p>元素及其所有子元素,而 rang2 包含<b>元素柑潦、文本節(jié)點 "Hello" 和文本節(jié)點 "world!"(如下圖所示)享言。
??在調(diào)用 selectNode() 時,startContainer渗鬼、endContainer 和 commonAncestorContainer 都等于傳入節(jié)點的父節(jié)點览露,也就是這個例子中的 document.body。
??而 startOffset 屬性等于給定節(jié)點在其父節(jié)點的 childNodes 集合中的索引(在這個例子中是 1——因為兼容 DOM 的瀏覽器將空格算作一個文本節(jié)點)譬胎,endOffset 等于 startOffset 加 1(因為只選擇了一個節(jié)點)差牛。
??在調(diào)用 selectNodeContents() 時,startContainer银择、endContainer 和 commonAncestorContainer 等于傳入的節(jié)點多糠,即這個例子中的<p>元素累舷。而 startOffset 屬性始終等于 0浩考,因為范圍從給定節(jié)點的第一個子節(jié)點開始。最后被盈,endOffset 等于子節(jié)點的數(shù)量(node.childNodes.length)析孽,在這個例子中是 2。
??此外只怎,為了更精細(xì)地控制將哪些節(jié)點包含在范圍中袜瞬,還可以使用下列方法。
- setStartBefore(refNode):將范圍的起點設(shè)置在 refNode 之前身堡,因此 refNode 也就是范圍選區(qū)中的第一個子節(jié)點邓尤。同時會將 startContainer 屬性設(shè)置為 refNode.parentNode,將 startOffset 屬性設(shè)置為 refNode 在其父節(jié)點的 childNodes 集合中的索引贴谎。
- setStartAfter(refNode):將范圍的起點設(shè)置在 refNode 之后汞扎,因此 refNode 也就不在范圍之內(nèi)了,其下一個同輩節(jié)點才是范圍選區(qū)中的第一個子節(jié)點擅这。同時會將 startContainer 屬性設(shè)置為 refNode.parentNode澈魄,將 startOffset 屬性設(shè)置為 refNode 在其父節(jié)點的 childNodes 集合中的索引加 1。
- setEndBefore(refNode):將范圍的終點設(shè)置在 refNode 之前仲翎,因此 refNode 也就不在范圍之內(nèi)了痹扇,其上一個同輩節(jié)點才是范圍選區(qū)中的最后一個子節(jié)點。同時會將 endContainer 屬性設(shè)置為 refNode.parentNode溯香,將 endOffset 屬性設(shè)置為 refNode 在其父節(jié)點的 childNodes 集合中的索引鲫构。
- setEndAfter(refNode):將范圍的終點設(shè)置在 refNode 之后,因此 refNode 也就是范圍選區(qū)中的最后一個子節(jié)點玫坛。同時會將 endContainer 屬性設(shè)置為 refNode.parentNode结笨,將 endOffset 屬性設(shè)置為 refNode 在其父節(jié)點的 childNodes 集合中的索引加 1。
??在調(diào)用這些方法時,所有屬性都會自動為你設(shè)置好禀梳。不過杜窄,要想創(chuàng)建復(fù)雜的范圍選區(qū),也可以直接指定這些屬性的值算途。
2. 用 DOM 范圍實現(xiàn)復(fù)雜選擇
??要創(chuàng)建復(fù)雜的范圍就得使用 setStart() 和 setEnd() 方法塞耕。這兩個方法都接受兩個參數(shù):一個參照節(jié)點和一個偏移量值。
??對 setStart() 來說嘴瓤,參照節(jié)點會變成 startContainer扫外,而偏移量值會變成 startOffset。
??對于 setEnd()來說廓脆,參照節(jié)點會變成 endContainer筛谚,而偏移量值會變成 endOffset。
??可以使用這兩個方法來模仿 selectNode() 和 selectNodeContents()停忿。來看下面的例子:
var range1 = document.createRange(),
range2 = document.createRange(),
p1 = document.getElementById("p1");
p1Index = -1,
i, len;
for (i=0, len=p1.parentNode.childNodes.length; i < len; i++) {
if (p1.parentNode.childNodes[i] == p1) {
p1Index = i;
break;
}
}
range1.setStart(p1.parentNode, p1Index);
range1.setEnd(p1.parentNode, p1Index + 1);
range2.setStart(p1, 0);
range2.setEnd(p1, p1.childNodes.length);
??顯然驾讲,要選擇這個節(jié)點(使用 range1),就必須確定當(dāng)前節(jié)點(p1)在其父節(jié)點的 childNodes 集合中的索引席赂。
??而要選擇這個節(jié)點的內(nèi)容(使用 range2)吮铭,也不必計算什么;只要通過 setStart() 和 setEnd()設(shè)置默認(rèn)值即可颅停。
??模仿 selectNode() 和 selectNodeContents() 并不是 setStart() 和 setEnd() 的主要用途谓晌,它們更勝一籌的地方在于能夠選擇節(jié)點的一部分。
??假設(shè)你只想選擇前面 HTML 示例代碼中從"Hello"的"llo"到"world!"的"o"——很容易做到癞揉。第一步是取得所有節(jié)點的引用纸肉,如下面的例子所示:
var p1 = document.getElementById("p1"),
helloNode = p1.firstChild.firstChild,
worldNode = p1.lastChild;
??實際上,"Hello"文本節(jié)點是<p>元素的孫子節(jié)點喊熟,因為它本身是<b>元素的一個子節(jié)點柏肪。因此,p1.firstChild 取得的是<b>逊移,而p1.firstChild.firstChild 取得的才是這個文本節(jié)點预吆。
??"world!"文本節(jié)點是<p>元素的第二個子節(jié)點(也是最后一個子節(jié)點),因此可以使用 p1.lastChild 取得該節(jié)點胳泉。
??然后拐叉,必須在創(chuàng)建范圍時指定相應(yīng)的起點和終點,如下面的例子所示扇商。
var range = document.createRange();
range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);
??因為這個范圍的選區(qū)應(yīng)該從"Hello"中"e"的后面開始凤瘦,所以在 setStart() 中傳入 helloNode 的同時,傳入了偏移量 2(即"e"的下一個位置案铺;"H"的位置是 0)蔬芥。
??設(shè)置選區(qū)的終點時,在 setEnd() 中傳入 worldNode 的同時傳入了偏移量 3,表示選區(qū)之外的第一個字符的位置笔诵,這個字符是"r"返吻,它的位置是 3(位置 0 上還有一個空格)。如下圖所示:
??由于 helloNode 和 worldNode 都是文本節(jié)點乎婿,因此它們分別變成了新建范圍的 startContainer 和 endContainer测僵。此時 startOffset 和 endOffset 分別用以確定兩個節(jié)點所包含的文本中的位置,而不是用以確定子節(jié)點的位置(就像傳入的參數(shù)為元素節(jié)點時那樣)谢翎。此時的 commonAncestorContainer 是<p>元素捍靠,也就是同時包含這兩個節(jié)點的第一個祖先元素。
??當(dāng)然森逮,僅僅是選擇了文檔中的某一部分用處并不大榨婆。但重要的是,選擇之后才可以對選區(qū)進(jìn)行操作褒侧。
3. 操作 DOM 范圍中的內(nèi)容
??在創(chuàng)建范圍時 良风,內(nèi)部會為這個范圍創(chuàng)建一個文檔片段,范圍所屬的全部節(jié)點都被添加到了這個文檔片段中璃搜。為了創(chuàng)建這個文檔片段拖吼,范圍內(nèi)容的格式必須正確有效。
??在前面的例子中这吻,我們創(chuàng)建的選區(qū)分別開始和結(jié)束于兩個文本節(jié)點的內(nèi)部,因此不能算是格式良好的 DOM 結(jié)構(gòu)篙议,也就無法通過 DOM 來表示唾糯。
??但是,范圍知道自身缺少哪些開標(biāo)簽和閉標(biāo)簽鬼贱,它能夠重新構(gòu)建有效的 DOM 結(jié)構(gòu)以便我們對其進(jìn)行操作移怯。
??對于前面的例子而言,范圍經(jīng)過計算知道選區(qū)中缺少一個開始的<b>標(biāo)簽这难,因此就會在后臺動態(tài)加入一個該標(biāo)簽舟误,同時還會在前面加入一個表示結(jié)束的</b>標(biāo)簽以結(jié)束"He"。于是姻乓,修改后的 DOM 就變成了如下所示嵌溢。
<p><b>He</b><b>llo</b> world!</p>
??另外,文本節(jié)點"world!"也被拆分為兩個文本節(jié)點蹋岩,一個包含"wo"赖草,另一個包含"rld!"。最終的 DOM 樹如下圖所示剪个,右側(cè)是表示范圍的文檔片段的內(nèi)容秧骑。
??像這樣創(chuàng)建了范圍之后,就可以使用各種方法對范圍的內(nèi)容進(jìn)行操作了(注意,表示范圍的內(nèi)部文檔片段中的所有節(jié)點乎折,都只是指向文檔中相應(yīng)節(jié)點的指針)绒疗。
??第一個方法,也是最容易理解的方法骂澄,就是 deleteContents()忌堂。這個方法能夠從文檔中刪除范圍所包含的內(nèi)容。例如:
var p1 = document.getElementById("p1"),
helloNode = p1.firstChild.firstChild,
worldNode = p1.lastChild,
range = document.createRange();
range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);
range.deleteContents();
??執(zhí)行以上代碼后酗洒,頁面中會顯示如下 HTML 代碼:
<p><b>He</b>rld!</p>
??由于范圍選區(qū)在修改底層 DOM 結(jié)構(gòu)時能夠保證格式良好士修,因此即使內(nèi)容被刪除了,最終的 DOM結(jié)構(gòu)依舊是格式良好的樱衷。
??與 deleteContents() 方法相似棋嘲,extractContents() 也會從文檔中移除范圍選區(qū)。
??但這兩個方法的區(qū)別在于矩桂,extractContents() 會返回范圍的文檔片段沸移。利用這個返回的值,可以將范圍的內(nèi)容插入到文檔中的其他地方侄榴。如下面的例子所示:
var p1 = document.getElementById("p1"),
helloNode = p1.firstChild.firstChild,
worldNode = p1.lastChild,
range = document.createRange();
range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);
var fragment = range.extractContents();
p1.parentNode.appendChild(fragment);
??在這個例子中雹锣,我們將提取出來的文檔片段添加到了文檔<body>元素的末尾。(記住癞蚕,在將文檔片段傳入 appendChild() 方法中時蕊爵,添加到文檔中的只是片段的子節(jié)點,而非片段本身桦山。)結(jié)果得到如下 HTML 代碼:
<p><b>He</b>rld!</p>
<b>llo</b> wo
??還一種做法攒射,即使用 cloneContents() 創(chuàng)建范圍對象的一個副本,然后在文檔的其他地方插入該副本恒水。如下面的例子所示:
var p1 = document.getElementById("p1"),
helloNode = p1.firstChild.firstChild,
worldNode = p1.lastChild,
range = document.createRange();
range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);
var fragment = range.cloneContents();
p1.parentNode.appendChild(fragment);
??這個方法與 extractContents() 非常類似会放,因為它們都返回文檔片段。它們的主要區(qū)別在于钉凌,cloneContents() 返回的文檔片段包含的是范圍中節(jié)點的副本咧最,而不是實際的節(jié)點。執(zhí)行上面的操作后御雕,頁面中的 HTML 代碼應(yīng)該如下所示:
<p><b>Hello</b> world!</p>
<b>llo</b> wo
??有一點請讀者注意矢沿,那就是在調(diào)用上面介紹的方法之前,拆分的節(jié)點并不會產(chǎn)生格式良好的文檔片段饮笛。換句話說咨察,原始的 HTML 在 DOM 被修改之前會始終保持不變。
4. 插入 DOM 范圍中的內(nèi)容
??利用范圍福青,可以刪除或復(fù)制內(nèi)容摄狱,還可以像前面介紹的那樣操作范圍中的內(nèi)容脓诡。使用 insertNode() 方法可以向范圍選區(qū)的開始處插入一個節(jié)點。假設(shè)我們想在前面例子中的 HTML 前面插入以下 HTML 代碼:
<span style="color: red">Inserted text</span>
??那么媒役,就可以使用下列代碼:
var p1 = document.getElementById("p1"),
helloNode = p1.firstChild.firstChild,
worldNode = p1.lastChild,
range = document.createRange();
range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);
var span = document.createElement("span");
span.style.color = "red";
span.appendChild(document.createTextNode("Inserted text"));
range.insertNode(span);
??運行以上 JavaScript 代碼祝谚,就會得到如下 HTML 代碼:
<p id="p1"><b>He<span style="color: red">Inserted text</span>llo</b> world</p>
??注意,<span>正好被插入到了"Hello"中的"llo"前面酣衷,而該位置就是范圍選區(qū)的開始位置何荚。
??還要注意的是开呐,由于這里沒有使用上一節(jié)介紹的方法壳嚎,結(jié)果原始的 HTML 并沒有添加或刪除<b>元素框舔。
??使用這種技術(shù)可以插入一些幫助提示信息,例如在打開新窗口的鏈接旁邊插入一幅圖像啊片。
??除了向范圍內(nèi)部插入內(nèi)容之外只锻,還可以環(huán)繞范圍插入內(nèi)容,此時就要使用 surroundContents() 方法紫谷。這個方法接受一個參數(shù)齐饮,即環(huán)繞范圍內(nèi)容的節(jié)點。在環(huán)繞范圍插入內(nèi)容時笤昨,后臺會執(zhí)行下列步驟祖驱。
??(1) 提取出范圍中的內(nèi)容(類似執(zhí)行 extractContent());
??(2) 將給定節(jié)點插入到文檔中原來范圍所在的位置上瞒窒;
??(3) 將文檔片段的內(nèi)容添加到給定節(jié)點中捺僻。
??可以使用這種技術(shù)來突出顯示網(wǎng)頁中的某些詞句,例如下列代碼:
var p1 = document.getElementById("p1"),
helloNode = p1.firstChild.firstChild,
worldNode = p1.lastChild,
range = document.createRange();
range.selectNode(helloNode);
var span = document.createElement("span");
span.style.backgroundColor = "yellow";
range.surroundContents(span);
??會給范圍選區(qū)加上一個黃色的背景根竿。得到的 HTML 代碼如下所示:
<p><b><span style="background-color:yellow">Hello</span></b> world!</p>
??為了插入<span>陵像,范圍必須包含整個 DOM 選區(qū)(不能僅僅包含選中的 DOM 節(jié)點)。
5. 折疊 DOM 范圍
??所謂折疊范圍寇壳,就是指范圍中未選擇文檔的任何部分∑拊酰可以用文本框來描述折疊范圍的過程壳炎。
??假設(shè)文本框中有一行文本,你用鼠標(biāo)選擇了其中一個完整的單詞逼侦。然后匿辩,你單擊鼠標(biāo)左鍵,選區(qū)消失榛丢,而光標(biāo)則落在了其中兩個字母之間铲球。
??同樣,在折疊范圍時晰赞,其位置會落在文檔中的兩個部分之間稼病,可能是范圍選區(qū)的開始位置选侨,也可能是結(jié)束位置。下圖展示了折疊范圍時發(fā)生的情形然走。
??使用 collapse() 方法來折疊范圍援制,這個方法接受一個參數(shù),一個布爾值芍瑞,表示要折疊到范圍的哪一端晨仑。參數(shù) true 表示折疊到范圍的起點,參數(shù) false 表示折疊到范圍的終點拆檬。
??要確定范圍已經(jīng)折疊完畢洪己,可以檢查 collapsed 屬性,如下所示:
range.collapse(true); // 折疊到起點
console.log(range.collapsed); // 輸出 true
??檢測某個范圍是否處于折疊狀態(tài)竟贯,可以幫我們確定范圍中的兩個節(jié)點是否緊密相鄰答捕。例如,對于下面的 HTML 代碼:
<p id="p1">Paragraph 1</p><p id="p2">Paragraph 2</p>
??如果我們不知道其實際構(gòu)成(比如說澄耍,這行代碼是動態(tài)生成的)噪珊,那么可以像下面這樣創(chuàng)建一個范圍。
var p1 = document.getElementById("p1"),
p2 = document.getElementById("p2"),
range = document.createRange();
range.setStartAfter(p1);
range.setStartBefore(p2);
console.log(range.collapsed); // 輸出 true
??在上述例子中齐莲,新創(chuàng)建的范圍是折疊的痢站,因為 p1 的后面和 p2 的前面什么也沒有。
6. 比較 DOM 范圍
??在有多個范圍的情況下选酗,可以使用 compareBoundaryPoints() 方法來確定這些范圍是否有公共的邊界(起點或終點)阵难。這個方法接受兩個參數(shù):表示比較方式的常量值和要比較的范圍。
??compareBoundaryPoints 表示比較方式的常量值如下所示芒填。
- Range.START_TO_START(0):比較第一個范圍和第二個范圍的起點呜叫;
- Range.START_TO_END(1):比較第一個范圍的起點和第二個范圍的終點;
- Range.END_TO_END(2):比較第一個范圍和第二個范圍的終點殿衰;
- Range.END_TO_START(3):比較第一個范圍的終點和第一個范圍的起點朱庆。
??compareBoundaryPoints() 方法可能的返回值如下:如果第一個范圍中的點位于第二個范圍中的點之前,返回 -1闷祥;如果兩個點相等娱颊,返回 0;如果第一個范圍中的點位于第二個范圍中的點之后凯砍,返回 1箱硕。示例:
var range1 = document.createRange();
var range2 = document.createRange();
var p1 = document.getElementById("p1");
range1.selectNodeContents(p1);
range2.selectNodeContents(p1);
range2.setEndBefore(p1.lastChild);
console.log(range1.compareBoundaryPoints(Range.START_TO_START, range2)); // 0
console.log(range1.compareBoundaryPoints(Range.END_TO_END, range2)); // 1
??在上述例子中,兩個范圍的起點實際上是相同的悟衩,因為它們的起點都是由 selectNodeContents() 方法設(shè)置的默認(rèn)值來指定的剧罩。因此,第一次比較返回 0座泳。但是惠昔,range2 的終點由于調(diào)用 setEndBefore() 已經(jīng)改變了幕与,結(jié)果是 range1 的終點位于 range2 的終點后面(如下圖所示),因此第二次比較返回 1舰罚。
7. 復(fù)制 DOM 范圍
??可以使用 cloneRange() 方法復(fù)制范圍纽门。這個方法會創(chuàng)建調(diào)用它的范圍的一個副本。
var newRange = range.cloneRange();
??新創(chuàng)建的范圍與原來的范圍包含相同的屬性营罢,而修改它的端點不會影響原來的范圍赏陵。
8. 清理 DOM 范圍
??在使用完范圍之后,最好是調(diào)用 detach() 方法饲漾,以便從創(chuàng)建范圍的文檔中分離出該范圍蝙搔。
??調(diào)用 detach()之后,就可以放心地解除對范圍的引用考传,從而讓垃圾回收機(jī)制回收其內(nèi)存了吃型。來看下面的例子。
range.detach(); // 從文檔中分離
range = null; // 解除引用
??在使用范圍的最后再執(zhí)行這兩個步驟是我們推薦的方式僚楞。一旦分離范圍勤晚,就不能再恢復(fù)使用了。
4.2泉褐、 IE8 及更早版本中的范圍
??雖然 IE9 支持 DOM 范圍赐写,但 IE8 及之前版本不支持 DOM 范圍。不過膜赃,IE8 及早期版本支持一種類似的概念挺邀,即文本范圍(text range)。
??文本范圍是 IE 專有的特性跳座,其他瀏覽器都不支持端铛。顧名思義,文本范圍處理的主要是文本(不一定是 DOM 節(jié)點)疲眷。通過<body>禾蚕、<button>、<input> 和 <textarea> 等這幾個元素狂丝,可以調(diào)用 createTextRange() 方法來創(chuàng)建文本范圍夕膀。以下是一個例子:
var range = document.body.createTextRange();
??像這樣通過 document 創(chuàng)建的范圍可以在頁面中的任何地方使用(通過其他元素創(chuàng)建的范圍則只能在相應(yīng)的元素中使用)。與 DOM 范圍類似美侦,使用 IE 文本范圍的方式也有很多種。
1. 用 IE 范圍實現(xiàn)簡單的選擇
??選擇頁面中某一區(qū)域的最簡單方式魂奥,就是使用范圍的 findText() 方法菠剩。這個方法會找到第一次出現(xiàn)的給定文本,并將范圍移過來以環(huán)繞該文本耻煤。如果沒有找到文本具壮,這個方法返回 false准颓;否則返回 true。同樣棺妓,仍然以下面的 HTML 代碼為例攘已。
<p id="p1"><b>Hello</b> world!</p>
??要選擇"Hello",可以使用下列代碼怜跑。
var range = document.body.createTextRange();
var found = range.findText("Hello");
??在執(zhí)行完第二行代碼之后样勃,文本"Hello"就被包圍在范圍之內(nèi)了。為此性芬,可以檢查范圍的 text 屬性來確認(rèn)(這個屬性返回范圍中包含的文本)峡眶,或者也可以檢查 findText() 的返回值——在找到了文本的情況下返回值為 true。例如:
console.log(found); // true
console.log(range.text); // "Hello"
??還可以為 findText() 傳入另一個參數(shù)植锉,即一個表示向哪個方向繼續(xù)搜索的數(shù)值辫樱。負(fù)值表示應(yīng)該從當(dāng)前位置向后搜索,而正值表示應(yīng)該從當(dāng)前位置向前搜索俊庇。因此狮暑,要查找文檔中前兩個"Hello"的實例,應(yīng)該使用下列代碼辉饱。
var found = range.findText("Hello");
var foundAgain = range.findText("Hello", 1);
??IE 中與 DOM 中的 selectNode() 方法最接近的方法是 moveToElementText()搬男,這個方法接受一個 DOM 元素,并選擇該元素的所有文本鞋囊,包括 HTML 標(biāo)簽止后。下面是一個例子。
var range = document.body.createTextRange();
var p1 = document.getElementById("p1");
range.moveToElementText(p1);
??在文本范圍中包含 HTML 的情況下溜腐,可以使用 htmlText 屬性取得范圍的全部內(nèi)容译株,包括 HTML 和文本,如下面的例子所示挺益。
console.log(range.htmlText);
??IE 的范圍沒有任何屬性可以隨著范圍選區(qū)的變化而動態(tài)更新歉糜。不過,其 parentElement() 方法倒是與 DOM 的 commonAncestorContainer 屬性類似望众。
var ancestor = range.parentElement();
??這樣得到的父元素始終都可以反映文本選區(qū)的父節(jié)點匪补。
2. 使用 IE 范圍實現(xiàn)復(fù)雜的選擇
??在 IE 中創(chuàng)建復(fù)雜范圍的方法,就是以特定的增量向四周移動范圍烂翰。為此夯缺,IE 提供了 4 個方法:move()、moveStart()甘耿、moveEnd() 和 expand()踊兜。這些方法都接受兩個參數(shù):移動單位和移動單位的數(shù)量。其中佳恬,移動單位是下列一種字符串值捏境。
- "character":逐個字符地移動于游。
- "word":逐個單詞(一系列非空格字符)地移動。
- "sentence":逐個句子(一系列以句號垫言、問號或嘆號結(jié)尾的字符)地移動贰剥。
- "textedit":移動到當(dāng)前范圍選區(qū)的開始或結(jié)束位置。
??通過 moveStart() 方法可以移動范圍的起點筷频,通過 moveEnd() 方法可以移動范圍的終點蚌成,移動的幅度由單位數(shù)量指定,如下面的例子所示截驮。
range.moveStart("word", 2); // 起點移動 2 個單詞
range.moveEnd("character", 1); // 終點移動 1 個字符
??使用 expand() 方法可以將范圍規(guī)范化笑陈。換句話說,expand() 方法的作用是將任何部分選擇的文本全部選中葵袭。
??例如涵妥,當(dāng)前選擇的是一個單詞中間的兩個字符,調(diào)用 expand("word") 可以將整個單詞都包含在范圍之內(nèi)坡锡。
??而 move() 方法則首先會折疊當(dāng)前范圍(讓起點和終點相等)蓬网,然后再將范圍移動指定的單位數(shù)量,如下面的例子所示鹉勒。
range.move("character", 5); // 移動 5 個字符
??調(diào)用 move() 之后帆锋,范圍的起點和終點相同,因此必須再使用 moveStart() 或 moveEnd() 創(chuàng)建新的選區(qū)禽额。
3. 操作 IE 范圍中的內(nèi)容
??在 IE 中操作范圍中的內(nèi)容可以使用 text 屬性或 pasteHTML() 方法锯厢。
??如前所述,通過 text 屬性可以取得范圍中的內(nèi)容文本脯倒;但是实辑,也可以通過這個屬性設(shè)置范圍中的內(nèi)容文本。示例:
var range = document.body.createTextRange();
range.findText("Hello");
range.text = "Howdy";
??如果仍以前面的 Hello World 代碼為例藻丢,執(zhí)行以上代碼后的 HTML 代碼如下剪撬。
<p id="p1"><b>Howdy</b> world!</p>
??注意,在設(shè)置 text 屬性的情況下悠反,HTML 標(biāo)簽保持不變残黑。
??要向范圍中插入 HTML 代碼,就得使用 pasteHTML() 方法斋否,如下面的例子所示梨水。
var range = document.body.createTextRange();
range.findText("Hello");
range.pasteHTML("<em>Howdy</em>");
??執(zhí)行這些代碼后,會得到如下 HTML茵臭。
<p id="p1"><b><em>Howdy</em></b> world!</p>
??不過冰木,在范圍中包含 HTML 代碼時,不應(yīng)該使用 pasteHTML(),因為這樣很容易導(dǎo)致不可預(yù)料的結(jié)果——很可能是格式不正確的 HTML踊沸。
4. 折疊 IE 范圍
??IE 為范圍提供的 collapse() 方法與相應(yīng)的 DOM 方法用法一樣:傳入 true 把范圍折疊到起點,傳入 false 把范圍折疊到終點社证。例如:
range.collapse(true); // 折疊到起點
??可惜的是逼龟,沒有對應(yīng)的 collapsed 屬性讓我們知道范圍是否已經(jīng)折疊完畢。為此追葡,必須使用 boundingWidth 屬性腺律,該屬性返回范圍的寬度(以像素為單位)。如果 boundingWidth 屬性等于 0宜肉,就說明范圍已經(jīng)折疊了:
var isCollapsed = (range.boundingWidth == 0);
??此外匀钧,還有 boundingHeight、boundingLeft 和 boundingTop 等屬性谬返,雖然它們都不像 boundingWidth 那么有用之斯,但也可以提供一些有關(guān)范圍位置的信息。
5. 比較 IE 范圍
??IE 中的 compareEndPoints() 方法與 DOM 范圍的 compareBoundaryPoints() 方法類似遣铝。這個方法接受兩個參數(shù):比較的類型和要比較的范圍佑刷。
??比較類型的取值范圍是下列幾個字符串值:"StartToStart"、 "StartToEnd"酿炸、 "EndToEnd" 和 "EndToStart"瘫絮。 這幾種比較類型與比較 DOM 范圍時使用的幾個值是相同的。
??同樣與 DOM 類似的是填硕,compareEndPoints() 方法也會按照相同的規(guī)則返回值麦萤,即如果第一個范圍的邊界位于第二個范圍的邊界前面,返回-1扁眯;如果二者邊界相同壮莹,返回 0;如果第一個范圍的邊界位于第二個范圍的邊界后面恋拍,返回 1垛孔。
??仍以前面的 Hello World 代碼為例,下列代碼將創(chuàng)建兩個范圍施敢,一個
選擇"Hello world!"(包括<b>標(biāo)簽)周荐,另一個選擇"Hello"。
var range1 = document.body.createTextRange();
var range2 = document.body.createTextRange();
range1.findText("Hello world!");
range2.findText("Hello");
console.log(range1.compareEndPoints("StartToStart", range2)); // 0
console.log(range1.compareEndPoints("EndToEnd", range2)); // 1
??由于這兩個范圍共享同一個起點僵娃,所以使用 compareEndPoints() 比較起點返回 0概作。而 range1 的終點在 range2 的終點后面,所以 compareEndPoints() 返回 1默怨。
??IE 中還有兩個方法讯榕,也是用于比較范圍的:isEqual() 用于確定兩個范圍是否相等,inRange() 用于確定一個范圍是否包含另一個范圍。下面是相應(yīng)的示例愚屁。
var range1 = document.body.createTextRange();
var range2 = document.body.createTextRange();
range1.findText("Hello World");
range2.findText("Hello");
console.log("range1.isEqual(range2): " + range1.isEqual(range2)); // false
console.log("range1.inRange(range2):" + range1.inRange(range2)); // true
??這個例子使用了與前面相同的范圍來示范這兩個方法济竹。由于這兩個范圍的終點不同,所以它們不相等霎槐,調(diào)用 isEqual() 返回 false送浊。由于 range2 實際位于 range1 內(nèi)部,它的終點位于后者的終點之前丘跌、起點之后袭景,所以 range2 被包含在 range1 內(nèi)部,調(diào)用 inRange() 返回 true闭树。
6. 復(fù)制 IE 范圍
??在 IE 中使用 duplicate() 方法可以復(fù)制文本范圍耸棒,結(jié)果會創(chuàng)建原范圍的一個副本,如下面的例子所示报辱。
var newRange = range.duplicate();
??新創(chuàng)建的范圍會帶有與原范圍完全相同的屬性与殃。
小結(jié)
??DOM2 級規(guī)范定義了一些模塊,用于增強(qiáng) DOM1 級捏肢∧巫眩“DOM2 級核心”為不同的 DOM 類型引入了一些與 XML 命名空間有關(guān)的方法。這些變化只在使用 XML 或 XHTML 文檔時才有用鸵赫;對于 HTML 文檔沒有實際意義衣屏。
??除了與 XML 命名空間有關(guān)的方法外,“DOM2 級核心”還定義了以編程方式創(chuàng)建 Document 實例的方法辩棒,也支持了創(chuàng)建 DocumentType 對象狼忱。
??“DOM2 級樣式”模塊主要針對操作元素的樣式信息而開發(fā),其特性簡要總結(jié)如下一睁。
- 每個元素都有一個關(guān)聯(lián)的 style 對象钻弄,可以用來確定和修改行內(nèi)的樣式。
- 要確定某個元素的計算樣式(包括應(yīng)用給它的所有 CSS 規(guī)則)者吁,可以使用 getComputedStyle() 方法窘俺。
- IE不支持 getComputedStyle() 方法,但為所有元素都提供了能夠返回相同信息 currentStyle 屬性复凳。
- 可以通過 document.styleSheets 集合訪問樣式表瘤泪。
- 除 IE 之外的所有瀏覽器都支持針對樣式表的這個接口,IE 也為幾乎所有相應(yīng)的 DOM 功能提供了自己的一套屬性和方法育八。
??“DOM2 級遍歷和范圍”模塊提供了與 DOM 結(jié)構(gòu)交互的不同方式对途,簡要總結(jié)如下。
- 遍歷即使用 NodeIterator 或 TreeWalker 對 DOM 執(zhí)行深度優(yōu)先的遍歷髓棋。
- NodeIterator 是一個簡單的接口实檀,只允許以一個節(jié)點的步幅前后移動惶洲。而 TreeWalker 在提供相同功能的同時,還支持在 DOM 結(jié)構(gòu)的各個方向上移動膳犹,包括父節(jié)點恬吕、同輩節(jié)點和子節(jié)點等方向。
- 范圍是選擇 DOM 結(jié)構(gòu)中特定部分镣奋,然后再執(zhí)行相應(yīng)操作的一種手段币呵。
- 使用范圍選區(qū)可以在刪除文檔中某些部分的同時,保持文檔結(jié)構(gòu)的格式良好侨颈,或者復(fù)制文檔中的相應(yīng)部分。
- IE8 及更早版本不支持“DOM2 級遍歷和范圍”模塊芯义,但它提供了一個專有的文本范圍對象哈垢,可以用來完成簡單的基于文本的范圍操作。IE9 完全支持 DOM 遍歷扛拨。