------------摘抄自他人筆記(http://itbilu.com/javascript/js/Vyxodm_1g.html、https://blog.csdn.net/pxy_lele/article/details/49755071)
感謝分享----------------------
目錄
DOM0、DOM1、DOM2、DOM3的區別
DOM0
JavaScript
在早期版本中提供了查詢和操作Web文檔的內容API(如:圖像和表單),在JavaScript
中定義了定義了'images'
、'forms'
等,因此我們可以像下這樣訪問第一張圖片或名爲“user”的表單:
document.images[0]
document.forms['user']
這實際上是未形成標準的試驗性質的初級階段的DOM,現在習慣上被稱爲DOM0
,即:第0級DOM
。由於DOM0
在W3C
進行標準備化之前出現,還處於未形成標準的初期階段,這時Netscape
和Microsoft
各自推出自己的第四代瀏覽器,自此DOM開始出各種問題。
DOM0與DHTML
Netscape Navigator 4
和IE4
分別發佈於1997年的6月和10月,這兩種瀏覽器都大幅擴展了DOM
,使JavaScript
的功能大大增加,而此時也開始出現一個新名詞:DHTML
。
DHTML
是Dynamic HTML
(動態HTML)的簡稱。DHTML
並不是一項新技術,而是將HTML、CSS、JavaScript技術組合的一種描述。即:
- 利用HTML把網頁標記爲各種元素
- 利用CSS設置元素樣式及其顯示位置
- 利用JavaScript操控頁面元素和樣式
利用DHTML,看起來可以很容易的控制頁面元素,並實現一此原本很複雜的效果(如:通過改變元素位置實現動畫)。但事實並非如此,因爲沒有規範和標準,兩種瀏覽器對相同功能的實現確完全不一樣。爲了保持程序的兼容性,程序員必須寫一些探查代碼以檢測JavaScript是運行於哪種瀏覽器之下,並提供與之對應的腳本。JavaScript
陷入了前所未有的混亂,DHTML
也因此在人們心中留下了很差的印象。
DOM1的出現
在瀏覽器廠商進行瀏覽器大站的同時,W3C
結合大家的優點推出了一個標準化的DOM
,並於1998年10月完成了第一級 DOM
,即:DOM1
。W3C
將DOM
定義爲一個與平臺和編程語言無關的接口,通過這個接口程序和腳本可以動態的訪問和修改文檔的內容、結構和樣式。
DOM1
級主要定義了HTML和XML文檔的底層結構。在DOM1
中,DOM由兩個模塊組成:DOM Core
(DOM核心)和DOM HTML
。其中,DOM Core
規定了基於XML的文檔結構標準,通過這個標準簡化了對文檔中任意部分的訪問和操作。DOM HTML
則在DOM核心的基礎上加以擴展,添加了針對HTML的對象和方法,如:JavaScript中的Document
對象
DOM2與DOM3
在DOM1
的基礎上DOM2
和DOM3
引入了更多的交互能力,也支持了更高級的XML特性。DOM2
和DOM3
將DOM分爲更多具有聯繫的模塊。DOM2級在原來DOM的基礎上又擴充了鼠標、用戶界面事件、範圍、遍歷等細分模塊,而且通過對象接口增加了對CSS的支持。DOM1
級中的DOM核心模塊也經過擴展開始支持XML命名空間。在DOM2
中引入了下列模塊,在模塊包含了衆多新類型和新接口:
DOM視圖
(DOM Views):定義了跟蹤不同文檔視圖的接口DOM事件
(DOM Events):定義了事件和事件處理的接口DOM樣式
(DOM Style):定義了基於CSS爲元素應用樣式的接口DOM遍歷和範圍
(DOM Traversal and Range):定義了遍歷和操作文檔樹的接口
DOM3
進一步擴展了DOM,在DOM3
中引入了以下模塊:
DOM加載和保存模塊
(DOM Load and Save):引入了以統一方式加載和保存文檔的方法DOM驗證模塊
(DOM Validation):定義了驗證文檔的方法DOM核心的擴展
(DOM Style):支持XML 1.0規範,涉及XML Infoset、XPath和XML Base
DOM2總結
DOM2級規範定義了一些模塊,用於增強 DOM1級。“DOM2級核心”爲不同的 DOM類型引入了 一些與 XML命名空間有關的方法。這些變化只在使用 XML或 XHTML文檔時纔有用;對於 HTML文 檔沒有實際意義。除了與 XML 命名空間有關的方法外, “DOM2 級核心”還定義了以編程方式創建 Document 實例的方法,也支持了創建 DocumentType 對象。
“DOM2級樣式”模塊主要針對操作元素的樣式信息而開發,其特性簡要總結如下。
- 每個元素都有一個關聯的 style 對象,可以用來確定和修改行內的樣式。
- 要確定某個元素的計算樣式(包括應用給它的所有 CSS規則),可以使用 getComputedStyle() 方法。
- IE不支持getComputedStyle()方法,但爲所有元素都提供了能夠返回相同信息 currentStyle 屬性。
- 可以通過 document.styleSheets 集合訪問樣式表。
- 除 IE之外的所有瀏覽器都支持針對樣式表的這個接口,IE也爲幾乎所有相應的 DOM功能提供 了自己的一套屬性和方法。
“DOM2級遍歷和範圍”模塊提供了與 DOM結構交互的不同方式,簡要總結如下。
- 遍歷即使用 NodeIterator 或 TreeWalker 對 DOM執行深度優先的遍歷。
- NodeIterator 是一個簡單的接口,只允許以一個節點的步幅前後移動。而 TreeWalker 在提 供相同功能的同時,還支持在 DOM結構的各個方向上移動,包括父節點、同輩節點和子節點等 方向。
- 範圍是選擇 DOM結構中特定部分,然後再執行相應操作的一種手段。
- 使用範圍選區可以在刪除文檔中某些部分的同時,保持文檔結構的格式良好,或者複製文檔中 的相應部分。
- IE8及更早版本不支持“DOM2級遍歷和範圍”模塊,但它提供了一個專有的文本範圍對象,可 以用來完成簡單的基於文本的範圍操作。IE9完全支持 DOM遍歷。
DOM2新增模塊-樣式
在 HTML中定義樣式的方式有 3種:通過<link/>元素包含外部樣式表文件、使用<style/>元素 定義嵌入式樣式,以及使用 style 特性定義針對特定元素的樣式。“DOM2級樣式”模塊圍繞這 3種應用 樣式的機制提供了一套 API。要確定瀏覽器是否支持 DOM2級定義的 CSS能力,可以使用下列代碼。
var supportsDOM2CSS = document.implementation.hasFeature("CSS", "2.0");
var supportsDOM2CSS2 = document.implementation.hasFeature("CSS2", "2.0");
在 style 特性中指定的任何 CSS屬性都將表現爲這個 style 對象的相應屬性。對於使用短劃線(分隔不同的詞彙,例如 background-image)的 CSS屬性 名,必須將其轉換成駝峯大小寫形式,才能通過 JavaScript來訪問。下表列出了幾個常見的 CSS屬性及 其在 style 對象中對應的屬性名。
注:float 是 JavaScript中的保留字,因此不能用作屬性名
“DOM2級樣式”規範規定 樣式對象上相應的屬性名應該是 cssFloat;Firefox、Safari、Opera和 Chrome都支持這個屬性,而 IE 支持的則是 styleFloat。
只要取得一個有效的DOM元素的引用,就可以隨時使用JavaScript爲其設置樣式。
var myDiv = document.getElementById("myDiv");
//設置背景顏色
myDiv.style.backgroundColor = "red";
//改變大小
myDiv.style.width = "100px"; myDiv.style.height = "200px";
//指定邊框
myDiv.style.border = "1px solid black";
// 在以這種方式改變樣式時,元素的外觀會自動被更新。
在標準模式下,所有度量值都必須指定一個度量單位。在混雜模式下,可以將 style.width 設置爲"20",瀏覽器會假設它是"20px";但在標準模式下,將 style.width 設置爲"20"會導致被忽略——因爲沒有度量單位。在實踐中,最好始 終都指定度量單位
1. DOM樣式屬性和方法
“DOM2級樣式”規範還爲style對象定義了一些屬性和方法。這些屬性和方法在提供元素的style 特性值的同時,也可以修改樣式。下面列出了這些屬性和方法。
- cssText:如前所述,通過它能夠訪問到 style 特性中的 CSS代碼。
- length:應用給元素的 CSS屬性的數量。
- parentRule:表示 CSS信息的 CSSRule 對象。本節後面將討論 CSSRule 類型。
- getPropertyCSSValue(propertyName):返回包含給定屬性值的 CSSValue 對象。
- getPropertyPriority(propertyName):如果給定的屬性使用了!important 設置,則返回 "important";否則,返回空字符串。 getPropertyValue(propertyName):返回給定屬性的字符串值。
- item(index):返回給定位置的 CSS屬性的名稱。
- removeProperty(propertyName):從樣式中刪除給定屬性。
- setProperty(propertyName,value,priority):將給定屬性設置爲相應的值,並加上優先 權標誌("important"或者一個空字符串)
通過cssText屬性可以訪問style特性中的CSS代碼。
在讀取模式下,cssText返回瀏覽器對style 特性中 CSS代碼的內部表示。
在寫入模式下,賦給 cssText 的值會重寫整個 style 特性的值;也就是 說,以前通過 style 特性指定的樣式信息都將丟失。例如,如果通過 style 特性爲元素設置了邊框, 然後再以不包含邊框的規則重寫 cssText,那麼就會抹去元素上的邊框。js的優先級高於css。
2. 計算的樣式
雖然 style 對象能夠提供支持 style 特性的任何元素的樣式信息,但它不包含那些從其他樣式表 層疊而來並影響到當前元素的樣式信息。“DOM2 級樣式”增強了 document.defaultView,提供了 getComputedStyle()方法。這個方法接受兩個參數:要取得計算樣式的元素和一個僞元素字符串(例 如":after")。
如果不需要僞元素信息,第二個參數可以是 null。getComputedStyle()方法返回一 個 CSSStyleDeclaration 對象(與 style 屬性的類型相同),其中包含當前元素的所有計算的樣式。
<!--下面這個 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>
應用給這個例子中<div>元素的樣式一方面來自嵌入式樣式表(<style>元素中的樣式),另一方 面來自其 style 特性。但是,style 特性中設置了 backgroundColor 和 border,沒有設置 width 和 height,後者是通過樣式表規則應用的。以下代碼可以取得這個元素計算後的樣式。
var myDiv = document.getElementById("myDiv");
var computedStyle = document.defaultView.getComputedStyle(myDiv, null);
alert(computedStyle.backgroundColor); // "red"
alert(computedStyle.width); // "100px"
alert(computedStyle.height); // "200px"
alert(computedStyle.border); // 在某些瀏覽器中是"1px solid black"
不能指望某個 CSS 屬性的默認值在不同瀏覽器中是相同 的。如果你需要元素具有某個特定的默認值,應該手工在樣式表中指定該值。
操作樣式表
使用下面的代碼可以確定瀏覽器是否支持 DOM2級樣式表。
var supportsDOM2StyleSheets =
document.implementation.hasFeature("StyleSheets", "2.0");
CSSStyleSheet 繼承自 StyleSheet,後者可以作爲一個基礎接口來定義非 CSS 樣式表。從 StyleSheet 接口繼承而來的屬性如下。 disabled:表示樣式表是否被禁用的布爾值。這個屬性是可讀/寫的,將這個值設置爲 true 可 以禁用樣式表。
應用於文檔的所有樣式表是通過 document.styleSheets 集合來表示的。通過這個集合的 length 屬性可以獲知文檔中樣式表的數量,而通過方括號語法或 item()方法可以訪問每一個樣式表。來看一個 例子。
- href:如果樣式表是通過<link>包含的,則是樣式表的 URL;否則,是 null。
- media:當前樣式表支持的所有媒體類型的集合。與所有 DOM 集合一樣,這個集合也有一個 length 屬性和一個 item()方法。也可以使用方括號語法取得集合中特定的項。如果集合是空 列表,表示樣式表適用於所有媒體。在 IE中,media 是一個反映<link>和<style>元素 media 特性值的字符串。
- ownerNode:指向擁有當前樣式表的節點的指針,樣式表可能是在 HTML 中通過<link>或 <style/>引入的(在 XML中可能是通過處理指令引入的)。如果當前樣式表是其他樣式表通過 @import 導入的,則這個屬性值爲 null。IE不支持這個屬性。
- parentStyleSheet:在當前樣式表是通過@import 導入的情況下,這個屬性是一個指向導入 它的樣式表的指針。
- title:ownerNode 中 title 屬性的值。 type:表示樣式表類型的字符串。對 CSS樣式表而言,這個字符串是"type/css"。 除了 disabled 屬性之外,其他屬性都是隻讀的。在支持以上所有這些屬性的基礎上, CSSStyleSheet 類型還支持下列屬性和方法:
- cssRules:樣式表中包含的樣式規則的集合。IE不支持這個屬性,但有一個類似的 rules 屬性。
- ownerRule:如果樣式表是通過@import 導入的,這個屬性就是一個指針,指向表示導入的規 則;否則,值爲 null。IE不支持這個屬性。 deleteRule(index):刪除 cssRules 集合中指定位置的規則。IE 不支持這個方法,但支持 一個類似的 removeRule()方法。
- insertRule(rule,index):向 cssRules 集合中指定的位置插入 rule 字符串。IE不支持這 個方法,但支持一個類似的 addRule()方法。
var sheet = null;
for (var i=0, len=document.styleSheets.length; i < len; i++){
sheet = document.styleSheets[i];
alert(sheet.href);
}
以上代碼可以輸出文檔中使用的每一個樣式表的 href 屬性(<style>元素包含的樣式表沒有 href 屬性) 。 不同瀏覽器的 document.styleSheets 返回的樣式表也不同。所有瀏覽器都會包含<style>元素 和 rel 特性被設置爲"stylesheet"的<link>元素引入的樣式表。IE和 Opera也包含 rel 特性被設置 爲"alternate stylesheet"的<link>元素引入的樣式表。
DOM2新增模塊-遍歷
“DOM2級遍歷和範圍”模塊定義了兩個用於輔助完成順序遍歷 DOM結構的類型:NodeIterator 和 TreeWalker。這兩個類型能夠基於給定的起點對 DOM結構執行深度優先(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 遍歷是深度優先的 DOM 結構遍歷,也就是說,移動的方向至少有兩個(取決 於使用的遍歷類型)。遍歷以給定節點爲根,不可能向上超出 DOM樹的根節點。
從 document 開始依序向前,訪問的第一個節點是 document,訪問的後一個節點是包含 "world!"的文本節點。從文檔後的文本節點開始,遍歷可以反向移動到 DOM 樹的頂端。此時,訪 問的第一個節點是包含"Hello"的文本節點,訪問的後一個節點是 document 節點。NodeIterator 和 TreeWalker 都以這種方式執行遍歷。
NodeIterator
NodeIterator 類型是兩者中比較簡單的一個,可以使用 document.createNodeIterator()方 法創建它的新實例。
這個方法接受下列 4個參數。
- root:想要作爲搜索起點的樹中的節點。
- whatToShow:表示要訪問哪些節點的數字代碼。
- filter:是一個 NodeFilter 對象,或者一個表示應該接受還是拒絕某種特定節點的函數。
- entityReferenceExpansion:布爾值,表示是否要擴展實體引用。這個參數在 HTML 頁面 中沒有用,因爲其中的實體引用不能擴展。
whatToShow 參數是一個位掩碼,通過應用一或多個過濾器(filter)來確定要訪問哪些節點。這個 參數的值以常量形式在 NodeFilter 類型中定義,如下所示。
- NodeFilter.SHOW_ALL:顯示所有類型的節點。
- NodeFilter.SHOW_ELEMENT:顯示元素節點。
- NodeFilter.SHOW_ATTRIBUTE:顯示特性節點。由於DOM結構原因,實際上不能使用這個值。
- NodeFilter.SHOW_TEXT:顯示文本節點。
- NodeFilter.SHOW_CDATA_SECTION:顯示 CDATA節點。對 HTML頁面沒有用。
- NodeFilter.SHOW_ENTITY_REFERENCE:顯示實體引用節點。對 HTML頁面沒有用。
- NodeFilter.SHOW_ENTITYE:顯示實體節點。對 HTML頁面沒有用。
- NodeFilter.SHOW_PROCESSING_INSTRUCTION:顯示處理指令節點。對 HTML頁面沒有用。
- NodeFilter.SHOW_COMMENT:顯示註釋節點。
- NodeFilter.SHOW_DOCUMENT:顯示文檔節點。
- NodeFilter.SHOW_DOCUMENT_TYPE:顯示文檔類型節點。
- NodeFilter.SHOW_DOCUMENT_FRAGMENT:顯示文檔片段節點。對 HTML頁面沒有用。
- NodeFilter.SHOW_NOTATION:顯示符號節點。對 HTML頁面沒有用。
除了 NodeFilter.SHOW_ALL 之外,可以使用按位或操作符來組合多個選項,如下面的例子所示:
var whatToShow = NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT;
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Page Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<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>
</body>
<script src="./static/main.js"></script>
</html>
/*main.js*/
var div = document.getElementById("div1");
//
var filter = function (node) {
return node.tagName.toLowerCase() == "li" ?
NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
};
//filter是過濾條件,下面iterator中filter改成null,則表示對輸出沒有輸出限制
var iterator = document.createNodeIterator(div, NodeFilter.SHOW_ELEMENT,
filter, false);
var node = iterator.nextNode();
while (node !== null) {
document.write (node.tagName+" "); //輸出標籤名
node = iterator.nextNode();
}
由於 nextNode()和 previousNode()方法都基於 NodeIterator 在 DOM結構中的內部指針工 作,所以 DOM結構的變化會反映在遍歷的結果中。
TreeWalker
TreeWalker 是 NodeIterator 的一個更高級的版本。除了包括 nextNode()和 previousNode() 在內的相同的功能之外,這個類型還提供了下列用於在不同方向上遍歷 DOM結構的方法。
- parentNode():遍歷到當前節點的父節點;
- firstChild():遍歷到當前節點的第一個子節點;
- lastChild():遍歷到當前節點的後一個子節點;
- nextSibling():遍歷到當前節點的下一個同輩節點;
- previousSibling():遍歷到當前節點的上一個同輩節點。
創建 TreeWalker 對象要使用 document.createTreeWalker()方法,這個方法接受的 4個參數 與 document.createNodeIterator()方法相同:作爲遍歷起點的根節點、要顯示的節點類型、過濾 器和一個表示是否擴展實體引用的布爾值。由於這兩個創建方法很相似,所以很容易用 TreeWalker 來代替 NodeIterator,如下面的例子所示。
var div = document.getElementById("div1");
var filter = function(node){ /*條件建立*/
return node.tagName.toLowerCase() == "li"?
NodeFilter.FILTER_ACCEPT :
NodeFilter.FILTER_SKIP;
};
/*,filter 可以返回的值有所不同。除了 NodeFilter.FILTER_ACCEPT 和
NodeFilter. FILTER_SKIP 之外,還可以使用 NodeFilter.FILTER_REJECT。
在使用 NodeIterator 對象時, NodeFilter.FILTER_SKIP 與 NodeFilter.FILTER_REJECT 的作用相同:跳過指定的節點。
但在使 用 TreeWalker 對象時,NodeFilter.FILTER_SKIP 會跳過相應節點繼續前進到子樹中的下一個節
點, 而 NodeFilter.FILTER_REJECT 則會跳過相應節點及該節點的整個子樹。例如,將前面例子中的
NodeFilter.FILTER_SKIP 修改成 NodeFilter.FILTER_REJECT,結果就是不會訪問任何節點。這是 因爲
第一個返回的節點是<div>,它的標籤名不是"li",於是就會返回 NodeFilter.FILTER_REJECT, 這意味着
遍歷會跳過整個子樹。在這個例子中,<div>元素是遍歷的根節點,於是結果就會停止遍歷。 */
var walker= document.createTreeWalker(div, NodeFilter.SHOW_ELEMENT, filter, false);
var node = iterator.nextNode();
while (node !== null) {
alert(node.tagName); //輸出標籤名
node = iterator.nextNode();
}
TreeWalker 真正強大的地方在於能夠在 DOM結構中沿任何方向移動。使用 TreeWalker 遍歷 DOM樹,即使不定義過濾器,也可以取得所有<li>元素,如下面的代碼所示
var div = document.getElementById("div1");
var walker = document.createTreeWalker(div, NodeFilter.SHOW_ELEMENT, null, false);
walker.firstChild();
//轉到<p>
walker.nextSibling();
// 轉到<ul>
var node = walker.firstChild();
//轉到第一個<li>
while (node !== null) {
alert(node.tagName);
node = walker.nextSibling();
}
TreeWalker 類型還有一個屬性,名叫 currentNode,表示任何遍歷方法在上一次遍歷中返回的 節點。通過設置這個屬性也可以修改遍歷繼續進行的起點
var node = walker.nextNode();
alert(node === walker.currentNode); //true
walker.currentNode = document.body; //修改起點
/* 與 NodeIterator 相比,TreeWalker 類型在遍歷 DOM時擁有更大的靈活性。
由於 IE中沒有對 應的類型和方法,所以使用遍歷的跨瀏覽器解決方案非常少見*/
DOM2新增模塊-範圍
“DOM2級遍歷和範圍”模塊定義了“範圍”(range)接口。
通過範圍可以選擇文檔中的一個區域,而不必考慮節點的界限(選擇在後臺完成,對用戶是不可見的) 。 在常規的 DOM操作不能更有效地修改文檔時,使用範圍往往可以達到目的。Firefox、Opera、Safari和 Chrome都支持 DOM範圍。IE以專有方式實現了自己的範圍特性。
DOM2級在 Document 類型中定義了 createRange()方法。在兼容 DOM的瀏覽器中,這個方法 屬於 document 對象。使用 hasFeature()或者直接檢測該方法,都可以確定瀏覽器是否支持範圍。
var supportsRange = document.implementation.hasFeature("Range", "2.0");
var alsoSupportsRange = (typeof document.createRange == "function");
如果瀏覽器支持範圍,那麼就可以使用 createRange()來創建 DOM範圍,如下所示:
var range = document.createRange();
與節點類似,新創建的範圍也直接與創建它的文檔關聯在一起,不能用於其他文檔。創建了範圍之 後,接下來就可以使用它在後臺選擇文檔中的特定部分。而創建範圍並設置了其位置之後,還可以針對 範圍的內容執行很多種操作,從而實現對底層 DOM樹的更精細的控制。
每個範圍由一個 Range 類型的實例表示,這個實例擁有很多屬性和方法。下列屬性提供了當前範 圍在文檔中的位置信息。
- startContainer:包含範圍起點的節點(即選區中第一個節點的父節點)。
- startOffset:範圍在 startContainer 中起點的偏移量。如果 startContainer 是文本節 點、註釋節點或 CDATA節點,那麼 startOffset 就是範圍起點之前跳過的字符數量。否則, startOffset 就是範圍中第一個子節點的索引。
- endContainer:包含範圍終點的節點(即選區中後一個節點的父節點)。
- endOffset:範圍在 endContainer 中終點的偏移量(與 startOffset 遵循相同的取值規則)。
- commonAncestorContainer:startContainer 和 endContainer 共同的祖先節點在文檔樹 中位置深的那個。
在把範圍放到文檔中特定的位置時,這些屬性都會被賦值。
1. 用 DOM範圍實現簡單選擇
要使用範圍來選擇文檔中的一部分,簡的方式就是使用 selectNode()或 selectNodeContents()。 這兩個方法都接受一個參數,即一個 DOM 節點,然後使用該節點中的信息來填充範圍。其中,selectNode()方法選擇整個節點,包括其子節點;而 selectNodeContents()方法則只選擇節點的 子節點。以下面的 HTML代碼爲例。
<!DOCTYPE html>
<html>
<body>
<p id="p1">
<b>Hello</b> world!
</p>
</body>
</html>
我們可以使用下列代碼來創建範圍
var range1 = document.createRange();
range2 = document.createRange();
p1 = document.getElementById("p1");
range1.selectNode(p1);
range2.selectNodeContents(p1);
這裏創建的兩個範圍包含文檔中不同的部分:rang1 包含<p/>元素及其所有子元素,而 rang2 包 含<b/>元素、文本節點"Hello"和文本節點"world!"(如圖 所示) 。
在調用 selectNode()時,startContainer、endContainer 和 commonAncestorContainer 都等於傳入節點的父節點,也就是這個例子中的 document.body。而 startOffset 屬性等於給定節 點在其父節點的 childNodes 集合中的索引(在這個例子中是 1——因爲兼容 DOM的瀏覽器將空格算 作一個文本節點),endOffset 等於 startOffset 加 1(因爲只選擇了一個節點)。
在調用 selectNodeContents()時,startContainer、endContainer 和 commonAncestorConta- iner 等於傳入的節點,即這個例子中的<p>元素。而 startOffset 屬性始終等於 0,因爲範圍從給定節 點的第一個子節點開始。後,endOffset 等於子節點的數量(node.childNodes.length),在這個例 子中是 2。
此外,爲了更精細地控制將哪些節點包含在範圍中,還可以使用下列方法。
- setStartBefore(refNode):將範圍的起點設置在 refNode 之前,因此 refNode 也就是範圍 選區中的第一個子節點。同時會將 startContainer 屬性設置爲 refNode.parentNode,將 startOffset 屬性設置爲 refNode 在其父節點的 childNodes 集合中的索引。
- setStartAfter(refNode):將範圍的起點設置在 refNode 之後,因此 refNode 也就不在範 圍之內了,其下一個同輩節點纔是範圍選區中的第一個子節點。同時會將 startContainer 屬 性設置爲 refNode.parentNode,將 startOffset 屬性設置爲 refNode 在其父節點的 childNodes 集合中的索引加 1。
- setEndBefore(refNode):將範圍的終點設置在 refNode 之前,因此 refNode 也就不在範圍 之內了,其上一個同輩節點纔是範圍選區中的後一個子節點。同時會將 endContainer 屬性設置爲refNode.parentNode,將endOffset 屬性設置爲refNode在其父節點的childNodes 集合中的索引。
- setEndAfter(refNode):將範圍的終點設置在 refNode 之後,因此 refNode 也就是範圍選區 中的後一個子節點。同時會將 endContainer 屬性設置爲 refNode.parentNode,將 endOffset 屬性設置爲 refNode 在其父節點的 childNodes 集合中的索引加 1。
在調用這些方法時,所有屬性都會自動爲你設置好。不過,要想創建複雜的範圍選區,也可以直接 指定這些屬性的值。
2. 用 DOM範圍實現複雜選擇
要創建複雜的範圍就得使用 setStart()和 setEnd()方法。這兩個方法都接受兩個參數:一個參 照節點和一個偏移量值。對 setStart()來說,參照節點會變成 startContainer,而偏移量值會變成 startOffset。對於 setEnd()來說,參照節點會變成 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);
顯然,要選擇這個節點(使用 range1),就必須確定當前節點(p1)在其父節點的 childNodes 集合中的索引。而要選擇這個節點的內容(使用 range2),也不必計算什麼;只要通過 setStart() 和 setEnd()設置默認值即可。模仿 selectNode()和 selectNodeContents()並不是 setStart() 和 setEnd()的主要用途,它們更勝一籌的地方在於能夠選擇節點的一部分。
假設你只想選擇前面 HTML 示例代碼中從"Hello"的"llo"到"world!"的"o"——很容易做到。 第一步是取得所有節點的引用,如下面的例子所示:
var p1 = document.getElementById("p1");
helloNode = p1.firstChild.firstChild;
worldNode = p1.lastChild;
實際上,"Hello"文本節點是<p>元素的孫子節點,因爲它本身是<b>元素的一個子節點。因此, p1.firstChild取得的是<b>,而p1.firstChild.firstChild取得的纔是這個文本節點。"world!" 文本節點是<p>元素的第二個子節點(也是後一個子節點),因此可以使用 p1.lastChild 取得該節點。然後,必須在創建範圍時指定相應的起點和終點,如下面的例子所示。
var range = document.createRange();
range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);
因爲這個範圍的選區應該從"Hello"中"e"的後面開始,所以在 setStart()中傳入 helloNode 的同時,傳入了偏移量 2(即"e"的下一個位置;"H"的位置是 0)。設置選區的終點時,在 setEnd() 中傳入 worldNode 的同時傳入了偏移量 3,表示選區之外的第一個字符的位置,這個字符是"r",它的 位置是 3(位置 0上還有一個空格)。如圖 12-7所示。
由於helloNode 和worldNode都是文本節點,因此它們分別變成了新建範圍的startContainer 和 endContainer。此時 startOffset 和 endOffset 分別用以確定兩個節點所包含的文本中的位置, 而不是用以確定子節點的位置(就像傳入的參數爲元素節點時那樣)。此時的 commonAncestor- Container 是<p>元素,也就是同時包含這兩個節點的第一個祖先元素。
當然,僅僅是選擇了文檔中的某一部分用處並不大。但重要的是,選擇之後纔可以對選區進行操作。
3. 操作 DOM範圍中的內容
在創建範圍時 ,內部會爲這個範圍創建一個文檔片段,範圍所屬的全部節點都被添加到了這個文檔 片段中。爲了創建這個文檔片段,範圍內容的格式必須正確有效。在前面的例子中,我們創建的選區分 別開始和結束於兩個文本節點的內部,因此不能算是格式良好的 DOM結構,也就無法通過 DOM來表 示。
但是,範圍知道自身缺少哪些開標籤和閉標籤,它能夠重新構建有效的 DOM結構以便我們對其進 行操作。 對於前面的例子而言,範圍經過計算知道選區中缺少一個開始的<b>標籤,因此就會在後臺動態加 入一個該標籤,同時還會在前面加入一個表示結束的</b>標籤以結束"He"。於是,修改後的 DOM 就 變成了如下所示。
<p><b>He</b><b>llo</b> world!</p>
另外,文本節點"world!"也被拆分爲兩個文本節點,一個包含"wo",另一個包含"rld!"。最終的 DOM樹如圖 12-8所示,右側是表示範圍的文檔片段的內容。
像這樣創建了範圍之後,就可以使用各種方法對範圍的內容進行操作了(注意,表示範圍的內部文 檔片段中的所有節點,都只是指向文檔中相應節點的指針)。
第一個方法,也是容易理解的方法,就是 deleteContents()。這個方法能夠從文檔中刪除範 圍所包含的內容。例如:
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();
執行以上代碼後,頁面中會顯示如下 HTML代碼:
<p><b>He</b>rld!</p>
由於範圍選區在修改底層 DOM 結構時能夠保證格式良好,因此即使內容被刪除了,終的 DOM 結構依舊是格式良好的。 與 deleteContents()方法相似,extractContents()也會從文檔中移除範圍選區。但這兩個方 法的區別在於,extractContents()會返回範圍的文檔片段。利用這個返回的值,可以將範圍的內容 插入到文檔中的其他地方。如下面的例子所示:
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()方法中時,添加到文檔中的只是片段的子節點,而非片段本身。)結果得到如下 HTML代碼:
<p><b>He</b>rld!</p> <b>llo</b> wo
還一種做法,即使用 cloneContents()創建範圍對象的一個副本,然後在文檔的其他地方插入該 副本。如下面的例子所示:
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()非常類似,因爲它們都返回文檔片段。它們的主要區別在於, cloneContents()返回的文檔片段包含的是範圍中節點的副本,而不是實際的節點。執行上面的操作 後,頁面中的 HTML代碼應該如下所示:
<p><b>Hello</b> world!</p> <b>llo</b> wo
有一點請讀者注意,那就是在調用上面介紹的方法之前,拆分的節點並不會產生格式良好的文檔片 段。換句話說,原始的 HTML在 DOM被修改之前會始終保持不變。
4. 插入 DOM範圍中的內容
利用範圍,可以刪除或複製內容,還可以像前面介紹的那樣操作範圍中的內容。使用 insertNode() 方法可以向範圍選區的開始處插入一個節點。假設我們想在前面例子中的 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"前面,而該位置就是範圍選區的開始位置。還要 注意的是,由於這裏沒有使用上一節介紹的方法,結果原始的 HTML並沒有添加或刪除<b>元素。使用 這種技術可以插入一些幫助提示信息,例如在打開新窗口的鏈接旁邊插入一幅圖像。 除了向範圍內部插入內容之外,還可以環繞範圍插入內容,此時就要使用 surroundContents() 方法。這個方法接受一個參數,即環繞範圍內容的節點。在環繞範圍插入內容時,後臺會執行下列 步驟。
(1) 提取出範圍中的內容(類似執行 extractContent());
(2) 將給定節點插入到文檔中原來範圍所在的位置上;
(3) 將文檔片段的內容添加到給定節點中。
可以使用這種技術來突出顯示網頁中的某些詞句,例如下列代碼:
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);
會給範圍選區加上一個黃色的背景。得到的 HTML代碼如下所示:
<p><b><span style="background-color:yellow">Hello</span></b> world!</p>
爲了插入<span>,範圍必須包含整個 DOM選區(不能僅僅包含選中的 DOM節點)。
5. 摺疊 DOM範圍
所謂摺疊範圍,就是指範圍中未選擇文檔的任何部分。可以用文本框來描述摺疊範圍的過程。假設 文本框中有一行文本,你用鼠標選擇了其中一個完整的單詞。然後,你單擊鼠標左鍵,選區消失,而光 標則落在了其中兩個字母之間。同樣,在摺疊範圍時,其位置會落在文檔中的兩個部分之間,可能是範 圍選區的開始位置,也可能是結束位置。
圖 12-9展示了摺疊範圍時發生的情形。 使用 collapse()方法來摺疊範圍,這個方法接受一個參數,一個布爾值,表示要摺疊到範圍的哪 一端。參數 true 表示摺疊到範圍的起點,參數 false 表示摺疊到範圍的終點。要確定範圍已經摺疊完 畢,可以檢查 collapsed 屬性,如下所示:
range.collapse(true); //摺疊到起點
alert(range.collapsed); //輸出 true
檢測某個範圍是否處於摺疊狀態,可以幫我們確定範圍中的兩個節點是否緊密相鄰。例如,對於下 面的 HTML代碼:
<p id="p1">Paragraph 1</p><p id="p2">Paragraph 2</p>
如果我們不知道其實際構成(比如說,這行代碼是動態生成的) ,那麼可以像下面這樣創建一個範圍。
var p1 = document.getElementById("p1"),
p2 = document.getElementById("p2"), range = document.createRange();
range.setStartAfter(p1);
range.setStartBefore(p2);
alert(range.collapsed); //輸出 true
在這個例子中,新創建的範圍是摺疊的,因爲 p1 的後面和 p2 的前面什麼也沒有。
6. 比較 DOM範圍
在有多個範圍的情況下,可以使用 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);
alert(range1.compareBoundaryPoints(Range.START_TO_START, range2)); //0
alert(range1.compareBoundaryPoints(Range.END_TO_END, range2)); //1
在這個例子中,兩個範圍的起點實際上是相同的,因爲它們的起點都是由 selectNodeContents() 方法設置的默認值來指定的。因此,第一次比較返回 0。但是,range2 的終點由於調用 setEndBefore() 已經改變了,結果是 range1 的終點位於 range2 的終點後面(見圖 12-10),因此第二次比較返回 1。
7. 複製 DOM範圍
可以使用 cloneRange()方法複製範圍。這個方法會創建調用它的範圍的一個副本。
var newRange = range.cloneRange();
新創建的範圍與原來的範圍包含相同的屬性,而修改它的端點不會影響原來的範圍。
8. 清理 DOM範圍
在使用完範圍之後,好是調用 detach()方法,以便從創建範圍的文檔中分離出該範圍。調用 detach()之後,就可以放心地解除對範圍的引用,從而讓垃圾回收機制回收其內存了。來看下面的 例子。
range.detach(); //從文檔中分離
range = null; //解除引用
在使用範圍的後再執行這兩個步驟是我們推薦的方式。一旦分離範圍,就不能再恢復使用了。
IE8及更早版本中的範圍
雖然 IE9支持 DOM範圍,但 IE8及之前版本不支持 DOM範圍。不過,IE8及早期版本支持一種類 似的概念,即文本範圍(text range)。文本範圍是 IE專有的特性,其他瀏覽器都不支持。顧名思義,文 本範圍處理的主要是文本(不一定是 DOM節點)。通過<body>、<button>、<input>和<textarea> 等這幾個元素,可以調用 createTextRange()方法來創建文本範圍。以下是一個例子:
var range = document.body.createTextRange();
像這樣通過 document 創建的範圍可以在頁面中的任何地方使用(通過其他元素創建的範圍則只能 在相應的元素中使用)。與 DOM範圍類似,使用 IE文本範圍的方式也有很多種。
1. 用 IE範圍實現簡單的選擇
選擇頁面中某一區域的簡單方式,就是使用範圍的 findText()方法。這個方法會找到第一次出 現的給定文本,並將範圍移過來以環繞該文本。如果沒有找到文本,這個方法返回 false;否則返回 true。同樣,仍然以下面的 HTML代碼爲例。
<p id="p1"><b>Hello</b> world!</p>
要選擇"Hello",可以使用下列代碼。
var range = document.body.createTextRange(); var found = range.findText("Hello");
在執行完第二行代碼之後,文本"Hello"就被包圍在範圍之內了。爲此,可以檢查範圍的 text 屬 性來確認(這個屬性返回範圍中包含的文本),或者也可以檢查 findText()的返回值——在找到了文 本的情況下返回值爲 true。例如:
alert(found); //true
alert(range.text); //"Hello"
還可以爲 findText()傳入另一個參數,即一個表示向哪個方向繼續搜索的數值。負值表示應該從 當前位置向後搜索,而正值表示應該從當前位置向前搜索。因此,要查找文檔中前兩個"Hello"的實例, 應該使用下列代碼。
var found = range.findText("Hello");
var foundAgain = range.findText("Hello", 1);
IE中與 DOM中的 selectNode()方法接近的方法是 moveToElementText(),這個方法接受一 個 DOM元素,並選擇該元素的所有文本,包括 HTML標籤。下面是一個例子。
var range = document.body.createTextRange();
var p1 = document.getElementById("p1");
range.moveToElementText(p1);
在文本範圍中包含 HTML的情況下,可以使用 htmlText 屬性取得範圍的全部內容,包括 HTML 和文本,如下面的例子所示。
alert(range.htmlText);
IE的範圍沒有任何屬性可以隨着範圍選區的變化而動態更新。不過,其 parentElement()方法倒 是與 DOM的 commonAncestorContainer 屬性類似。
var ancestor = range.parentElement();
這樣得到的父元素始終都可以反映文本選區的父節點。
2. 使用 IE範圍實現複雜的選擇
在 IE 中創建複雜範圍的方法,就是以特定的增量向四周移動範圍。爲此,IE 提供了 4 個方法: move()、moveStart()、moveEnd()和 expand()。這些方法都接受兩個參數:移動單位和移動單位 的數量。其中,移動單位是下列一種字符串值。
- "character":逐個字符地移動。
- "word":逐個單詞(一系列非空格字符)地移動。
- "sentence":逐個句子(一系列以句號、問號或歎號結尾的字符)地移動。
- "textedit":移動到當前範圍選區的開始或結束位置。
通過 moveStart()方法可以移動範圍的起點,通過 moveEnd()方法可以移動範圍的終點,移動的 幅度由單位數量指定,如下面的例子所示。
range.moveStart("word", 2); //起點移動 2 個單詞
range.moveEnd("character", 1); //終點移動 1 個字符
使用 expand()方法可以將範圍規範化。換句話說,expand()方法的作用是將任何部分選擇的文 本全部選中。例如,當前選擇的是一個單詞中間的兩個字符,調用 expand("word")可以將整個單詞都 包含在範圍之內。
而 move()方法則首先會摺疊當前範圍(讓起點和終點相等),然後再將範圍移動指定的單位數量, 如下面的例子所示。
range.move("character", 5); //移動 5 個字符
調用 move()之後,範圍的起點和終點相同,因此必須再使用 moveStart()或 moveEnd()創建新 的選區。
3. 操作 IE範圍中的內容
在 IE中操作範圍中的內容可以使用 text 屬性或 pasteHTML()方法。如前所述,通過 text 屬性 可以取得範圍中的內容文本;但是,也可以通過這個屬性設置範圍中的內容文本。來看一個例子。
var range = document.body.createTextRange();
range.findText("Hello");
range.text = "Howdy";
如果仍以前面的 Hello World代碼爲例,執行以上代碼後的 HTML代碼如下。
<p id="p1"><b>Howdy</b> world!</p>
注意,在設置 text 屬性的情況下,HTML標籤保持不變。 要向範圍中插入 HTML代碼,就得使用 pasteHTML()方法,如下面的例子所示。
var range = document.body.createTextRange();
range.findText("Hello");
range.pasteHTML("<em>Howdy</em>");
執行這些代碼後,會得到如下 HTML。
<p id="p1"><b><em>Howdy</em></b> world!</p>
不過,在範圍中包含 HTML代碼時,不應該使用 pasteHTML(),因爲這樣很容易導致不可預料的 結果——很可能是格式不正確的 HTML。
4. 摺疊 IE範圍
IE爲範圍提供的 collapse()方法與相應的 DOM方法用法一樣:傳入 true 把範圍摺疊到起點, 傳入 false 把範圍摺疊到終點。例如:
range.collapse(true); //摺疊到起點
可惜的是,沒有對應的 collapsed 屬性讓我們知道範圍是否已經摺疊完畢。爲此,必須使用 boundingWidth 屬性,該屬性返回範圍的寬度(以像素爲單位)。如果 boundingWidth 屬性等於 0, 就說明範圍已經摺疊了:
var isCollapsed = (range.boundingWidth == 0);
此外,還有 boundingHeight、boundingLeft 和 boundingTop 等屬性,雖然它們都不像 boundingWidth 那麼有用,但也可以提供一些有關範圍位置的信息。
5. 比較 IE範圍
IE中的 compareEndPoints()方法與 DOM範圍的 compareBoundaryPoints()方法類似。這個 方法接受兩個參數:比較的類型和要比較的範圍。比較類型的取值範圍是下列幾個字符串值:"StartToStart"、"StartToEnd"、"EndToEnd"和"EndToStart"。這幾種比較類型與比較 DOM範 圍時使用的幾個值是相同的。
同樣與 DOM類似的是,compareEndPoints()方法也會按照相同的規則返回值,即如果第一個範 圍的邊界位於第二個範圍的邊界前面,返回-1;如果二者邊界相同,返回 0;如果第一個範圍的邊界位 於第二個範圍的邊界後面,返回 1。仍以前面的 Hello World代碼爲例,下列代碼將創建兩個範圍,一個 選擇"Hello world!"(包括<b>標籤),另一個選擇"Hello"。
var range1 = document.body.createTextRange();
var range2 = document.body.createTextRange();
range1.findText("Hello world!"); range2.findText("Hello");
alert(range1.compareEndPoints("StartToStart", range2)); //0
alert(range1.compareEndPoints("EndToEnd", range2)); //1
由於這兩個範圍共享同一個起點,所以使用 compareEndPoints()比較起點返回 0。而 range1 的終點在 range2 的終點後面,所以 compareEndPoints()返回 1。 IE 中還有兩個方法,也是用於比較範圍的:isEqual()用於確定兩個範圍是否相等,inRange() 用於確定一個範圍是否包含另一個範圍。下面是相應的示例。
var range1 = document.body.createTextRange();
var range2 = document.body.createTextRange();
range1.findText("Hello World");
range2.findText("Hello");
alert("range1.isEqual(range2): " + range1.isEqual(range2)); //false
alert("range1.inRange(range2):" + range1.inRange(range2)); //true
這個例子使用了與前面相同的範圍來示範這兩個方法。由於這兩個範圍的終點不同,所以它們不相 等,調用 isEqual()返回 false。由於 range2 實際位於 range1 內部,它的終點位於後者的終點之 前、起點之後,所以 range2 被包含在 range1 內部,調用 inRange()返回 true。
6. 複製 IE範圍
在 IE中使用 duplicate()方法可以複製文本範圍,結果會創建原範圍的一個副本,如下面的例子 所示。
var newRange = range.duplicate();
新創建的範圍會帶有與原範圍完全相同的屬性。