重慶分公司,新征程啟航
為企業(yè)提供網(wǎng)站建設(shè)、域名注冊、服務(wù)器等服務(wù)
為企業(yè)提供網(wǎng)站建設(shè)、域名注冊、服務(wù)器等服務(wù)
這篇文章主要介紹Javascript中DOM范圍的示例分析,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!
十多年的海興網(wǎng)站建設(shè)經(jīng)驗(yàn),針對設(shè)計(jì)、前端、開發(fā)、售后、文案、推廣等六對一服務(wù),響應(yīng)快,48小時及時工作處理。成都全網(wǎng)營銷推廣的優(yōu)勢是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動調(diào)整海興建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計(jì),從而大程度地提升瀏覽體驗(yàn)。成都創(chuàng)新互聯(lián)從事“海興網(wǎng)站設(shè)計(jì)”,“海興網(wǎng)站推廣”以來,每個客戶項(xiàng)目都認(rèn)真落實(shí)執(zhí)行。
創(chuàng)建范圍
Document類型中定義了createRange()
方法。在兼容DOM的瀏覽器中,這個方法屬于document對象。使用hasFeature()
或者直接檢測該方法,都可以確定瀏覽器是否支持范圍
[注意]IE8-瀏覽器不支持
var supportsRange = document.implementation.hasFeature("Range", "2.0"); var alsoSupportsRange =(typeof document.createRange == "function");
如果瀏覽器支持范圍,那么就可以使用createRange()
來創(chuàng)建DOM范圍,如下所示
var range = document.createRange();
與節(jié)點(diǎn)類似,新創(chuàng)建的范圍也直接與創(chuàng)建它的文檔關(guān)聯(lián)在一起,不能用于其他文檔。創(chuàng)建了范圍之后,接下來就可以使用它在后臺選擇文檔中的特定部分。而創(chuàng)建范圍并設(shè)置了其位置之后,還可以針對范圍的內(nèi)容執(zhí)行很多種操作,從而實(shí)現(xiàn)對底層DOM樹的更精細(xì)的控制
每個范圍由一個Range類型的實(shí)例表示,這個實(shí)例擁有很多屬性和方法。
下列屬性提供了當(dāng)前范圍在文檔中的位置信息
startContainer:包含范圍起點(diǎn)的節(jié)點(diǎn)(即選區(qū)中第一個節(jié)點(diǎn)的父節(jié)點(diǎn))
startoffset:范圍在startContainer中起點(diǎn)的偏移量。如果startContainer是文本節(jié)點(diǎn)、注釋節(jié)點(diǎn)或CDATA節(jié)點(diǎn),那么startoffset就是范圍起點(diǎn)之前跳過的字符數(shù)量。否則,startoffset就是范圍中第一個子節(jié)點(diǎn)的索引
endContainer:包含范圍終點(diǎn)的節(jié)點(diǎn)(即選區(qū)中最后一個節(jié)點(diǎn)的父節(jié)點(diǎn))
endOffset:范圍在endContainer中終點(diǎn)的偏移量(與startoffset遵循相同的取值規(guī)則)
commonAncestorContainer:startContainer和endContainer共同的祖先節(jié)點(diǎn)在文檔樹中位置最深的那個
在把范圍放到文檔中特定的位置時,這些屬性都會被賦值
簡單選擇
要使用范圍來選擇文檔中的一部分,最簡單的方式就是使用selectNode()
或selectNodeContents()
。這兩個方法都接受一個參數(shù),即一個DOM節(jié)點(diǎn),然后使用該節(jié)點(diǎn)中的信息來填充范圍。其中,selectNode()
方法選擇整個節(jié)點(diǎn),包括其子節(jié)點(diǎn);而selectNodeContents()
方法則只選擇節(jié)點(diǎn)的子節(jié)點(diǎn)
Hello world!
我們可以使用下列代碼來創(chuàng)建范圍
var range1 = document.createRange(); var range2 = document.createRange(); var p1 = document.getElementById("p1"); //Range {startContainer: body, startOffset: 1, endContainer: body, endOffset: 2, collapsed: false…} range1.selectNode(p1); //Range {startContainer: p#p1, startOffset: 0, endContainer: p#p1, endOffset: 2, collapsed: false…} range2.selectNodeContents(p1);
這里創(chuàng)建的兩個范圍包含文檔中不同的部分:rangl包含
元素及其所有子元素,而rang2包含元素、文本節(jié)點(diǎn)"Hello"和文本節(jié)點(diǎn)"world!"
在調(diào)用selectNode()
時,startContainer、endContainer和commonAncestorContainer都等于傳入節(jié)點(diǎn)的父節(jié)點(diǎn),也就是這個例子中的document.body
。而startoffset屬性等于給定節(jié)點(diǎn)在其父節(jié)點(diǎn)的childNodes集合中的索引(在這個例子中是1——因?yàn)榧嫒軩OM的瀏覽器將空格算作一個文本節(jié)點(diǎn)),endOffset等于startoffset加1(因?yàn)橹贿x擇了一個節(jié)點(diǎn))
在調(diào)用selectNodeContents()
時,startContainer、endContainer和commonAncestorContainer等于傳入的節(jié)點(diǎn),即這個例子中的
元素。而startoffset屬性始終等于0,因?yàn)榉秶鷱慕o定節(jié)點(diǎn)的第一個子節(jié)點(diǎn)開始。最后,endOffset等于子節(jié)點(diǎn)的數(shù)量(node.childNodes.length
),在這個例子中是2
此外,為了更精細(xì)地控制將哪些節(jié)點(diǎn)包含在范圍中,還可以使用下列方法
setStartBefore(refNode)
:將范圍的起點(diǎn)設(shè)置在refNode之前,因此refNode也就是范圍選區(qū)中的第一個子節(jié)點(diǎn)。同時會將startContainer屬性設(shè)置為refNode.parentNode,將startoffset屬性設(shè)置為refNode在其父節(jié)點(diǎn)的childNodes集合中的索引
setStartAfter(refNode)
:將范圍的起點(diǎn)設(shè)置在refNode之后,因此refNode也就不在范圍之內(nèi)了,其下一個同輩節(jié)點(diǎn)才是范圍選區(qū)中的第一個子節(jié)點(diǎn)。同時會將startContainer屬性設(shè)置為refNode.parentNode,將startoffset屬性設(shè)置為refNode在其父節(jié)點(diǎn)的childNodes集合中的索引加1
setEndBefore(refNode)
:將范圍的終點(diǎn)設(shè)置在refNode之前,因此refNode也就不在范圍之內(nèi)了,其上一個同輩節(jié)點(diǎn)才是范圍選區(qū)中的最后一個子節(jié)點(diǎn)。同時會將endContainer屬性設(shè)置為refNode.parentNode,將endOffset屬性設(shè)置為refNode在其父節(jié)點(diǎn)的childNodes集合中的索引
setEndAfter(refNode)
:將范圍的終點(diǎn)設(shè)置在refNode之后,因此refNode也就是范圍選區(qū)中的最后一個子節(jié)點(diǎn)。同時會將endContainer屬性設(shè)置為refNode.parentNode,將endOffset屬性設(shè)置為refNode在其父節(jié)點(diǎn)的childNodes集合中的索引加1
調(diào)用這些方法時,所有屬性會自動設(shè)置好。不過,要想創(chuàng)建復(fù)雜的范圍選區(qū),也可以直接指定這些屬性的值
復(fù)雜選擇
要創(chuàng)建復(fù)雜的范圍就得使用setStart()
和setEnd()
方法。這兩個方法都接受兩個參數(shù):一個參照節(jié)點(diǎn)和一個偏移量值。對setStart()
來說,參照節(jié)點(diǎn)會變成startContainer。而偏移量值會變成startoffset。對于setEnd()
來說,參照節(jié)點(diǎn)會變成endContainer,而偏移量值會變成endOffset。可以使用這兩個方法來模仿selectNode()
和selectNodeContents()
。
來看下面的例子
var range1 = document.createRange(); var range2 = document.createRange(); var p1 = document.getElementById("p1"); var p1Index = -1; var 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é)點(diǎn)(使用range1),就必須確定當(dāng)前節(jié)點(diǎn)(p1)在其父節(jié)點(diǎn)的childNodes集合中的索引。而要選擇這個節(jié)點(diǎn)的內(nèi)容(使用range2),也不必計(jì)算什么;只要通過setStart()和setEnd()設(shè)置默認(rèn)值即可。模仿selectNode()
和selectNodeContents()
并不是setStart()
和setEnd()
的主要用途,它們更勝一籌的地方在于能夠選擇節(jié)點(diǎn)的一部分
假設(shè)只想選擇前面HTML示例代碼中從“Hello"的"llo"到"world!"的"o"——很容易做到
第一步是取得所有節(jié)點(diǎn)的引用,如下所示:
var p1 = document.getElementById("p1"); var helloNode = p1.firstChild.firstChild; var worldNode = p1.lastChild;
實(shí)際上,"Hello”文本節(jié)點(diǎn)是
元素的孫子節(jié)點(diǎn),因?yàn)樗旧硎?b>元素的一個子節(jié)點(diǎn)。因此,p1.firstchild
取得的是,而p1.firstchild.firstchild
取得的才是這個文本節(jié)點(diǎn)。"world!"文本節(jié)點(diǎn)是
元素的第二個子節(jié)點(diǎn)(也是最后一個子節(jié)點(diǎn)),因此可以使用p1.lastChild
取得該節(jié)點(diǎn)。
然后,必須在創(chuàng)建范圍時指定相應(yīng)的起點(diǎn)和終點(diǎn),如下所示
var range = document.createRange(); range.setStart(helloNode, 2); range.setEnd(worldNode, 3);
因?yàn)檫@個范圍的選區(qū)應(yīng)該從"Hello"中"e"的后面開始,所以在setStart()
中傳入helloNode的同時,傳入了偏移量2(即"e"的下一個位置;"H"的位置是0)。設(shè)置選區(qū)的終點(diǎn)時,在setEnd()
中傳入worldNode的同時傳入了偏移量3,表示選區(qū)之外的第一個字符的位置,這個字符是”r",它的位置是3(位置0上還有一個空格)。
如下所示
由于helloNode和worldNode都是文本節(jié)點(diǎn),因此它們分別變成了新建范圍的startContainer和endContainer。此時startoffset和endOffset分別用以確定兩個節(jié)點(diǎn)所包含的文本中的位置,而不是用以確定子節(jié)點(diǎn)的位置(就像傳入的參數(shù)為元素節(jié)點(diǎn)時那樣)。此時的commonAncestorContainer是
元素,也就是同時包含這兩個節(jié)點(diǎn)的第一個祖先元素
當(dāng)然,僅僅是選擇了文檔中的某一部分用處并不大。但重要的是,選擇之后才可以對選區(qū)進(jìn)行操作
操作范圍內(nèi)容
在創(chuàng)建范圍時,內(nèi)部會為這個范圍創(chuàng)建一個文檔片段,范圍所屬的全部節(jié)點(diǎn)都被添加到了這個文檔片段中。為了創(chuàng)建這個文檔片段,范圍內(nèi)容的格式必須正確有效。在前面的例子中,創(chuàng)建的選區(qū)分別開始和結(jié)束于兩個文本節(jié)點(diǎn)的內(nèi)部,因此不能算是格式良好的DOM結(jié)構(gòu),也就無法通過DOM來表示。但是,范圍知道自身缺少哪些開標(biāo)簽和閉標(biāo)簽,它能夠重新構(gòu)建有效的DOM結(jié)構(gòu)以便對其進(jìn)行操作
對于前面的例子而言,范圍經(jīng)過計(jì)算知道選區(qū)中缺少一個開始的標(biāo)簽,因此就會在后臺動態(tài)加入一個該標(biāo)簽,同時還會在前面加入一個表示結(jié)束的標(biāo)簽以結(jié)束"He"。于是,修改后的DOM就變成了如下所示
Hello world!
另外,文本節(jié)點(diǎn)"world!"也被拆分為兩個文本節(jié)點(diǎn),一個包含"wo",另一個包含"rid!"。最終的DOM樹下圖所示,右側(cè)是表示范圍的文檔片段的內(nèi)容
像這樣創(chuàng)建了范圍之后,就可以使用各種方法對范圍的內(nèi)容進(jìn)行操作了
[注意]表示范圍的內(nèi)部文檔片段中的所有節(jié)點(diǎn),都只是指向文檔中相應(yīng)節(jié)點(diǎn)的指針
【deleteContents()】
操作范圍內(nèi)容的第一個方法是deleteContents()
,這個方法能夠從文檔中刪除范圍所包含的內(nèi)容
var p1 = document.getElementById("p1"); var helloNode = p1.firstChild.firstChild; var worldNode = p1.lastChild; var range = document.createRange(); range.setStart(helloNode, 2); range.setEnd(worldNode, 3); range.deleteContents();
執(zhí)行以上代碼后,頁面中會顯示如下HTML代碼
Herld!
由于范圍選區(qū)在修改底層DOM結(jié)構(gòu)時能夠保證格式良好,因此即使內(nèi)容被刪除了,最終的DOM結(jié)構(gòu)依舊是格式良好的
【extractContents()】
與deleteContents()
方法相似,extractContents()
方法也會從文檔中移除范圍選區(qū)。但區(qū)別在于,extractContents()
會返回范圍的文檔片段。利用這個返回的值,可以將范圍的內(nèi)容插入到文檔中的其他地方。如下所示:
var p1 = document.getElementById("p1"); var helloNode = p1.firstChild.firstChild; var worldNode = p1.lastChild; var range = document.createRange(); range.setStart(helloNode, 2); range.setEnd(worldNode, 3); var fragment = range.extractContents(); p1.parentNode.appendChild(fragment);
在這個例子中,將提取出來的文檔片段添加到了文檔
元素的末尾[注意]在將文檔片段傳入appendChild()方法中時,添加到文檔中的只是片段的子節(jié)點(diǎn),而非片段本身
Herld!
llo wo
【cloneContents】
還有一種做法是使用cloneContents()
創(chuàng)建范圍對象的一個副本,然后在文檔其他地方插入該副本
var p1 = document.getElementById("p1"); var helloNode = p1.firstChild.firstChild; var worldNode = p1.lastChild; var range = document.createRange(); range.setStart(helloNode, 2); range.setEnd(worldNode, 3); var fragment = range.cloneContents(); p1.parentNode.appendChild(fragment);
這個方法與extractContents()
非常類似,因?yàn)樗鼈兌挤祷匚臋n片段。它們的主要區(qū)別在于,cloneContents()
返回的文檔片段包含的是范圍中節(jié)點(diǎn)的副本,而不是實(shí)際的節(jié)點(diǎn)。執(zhí)行上面的操作后,頁面中的HTML代碼如下所示:
Hello world!
llo wo
[注意]在調(diào)用cloneContents()方法之前,拆分的節(jié)點(diǎn)并不會產(chǎn)生格式良好的文檔片段。換句話說,原始的HTML在DOM被修改之前會始終保持不變
插入范圍內(nèi)容
利用范圍,可以刪除或復(fù)制內(nèi)容,還可以像前面介紹的那樣操作范圍中的內(nèi)容。使用insertNode()
方法可以向范圍選區(qū)的開始處插入一個節(jié)點(diǎn)。假設(shè)在前面例子中的HTML前面插入以下HTML代碼
Inserted text
那么可以使用下列代碼:
var p1 = document.getElementById("p1"); var helloNode = p1.firstChild.firstChild; var worldNode = p1.lastChild; var range = document.createRange(); var span = document.createElement("span"); range.setStart(helloNode, 2); range.setEnd(worldNode, 3); span.style.color = "red"; span.appendChild(document.createTextNode("Inserted text")); range.insertNode(span);
運(yùn)行以上javascript代碼,就會得到如下HTML代碼
HeInserted textllo world
[注意]正好被插入到了"Hello"中的"llo"前面,而該位置就是范圍選區(qū)的開始位置。使用這種技術(shù)可以插入一些幫助提示信息,例如在打開新窗口的鏈接旁邊插入一幅圖像
【surroundContents()】
除了向范圍內(nèi)部插入內(nèi)容之外,還可以環(huán)繞范圍插入內(nèi)容,此時就要使用surroundContents()
方法。這個方法接受一個參數(shù),即環(huán)繞范圍內(nèi)容的節(jié)點(diǎn)。在環(huán)繞范圍插入內(nèi)容時,后臺會執(zhí)行下列步驟
1、提取出范圍中的內(nèi)容(類似執(zhí)行extractContents()
)
2、將給定節(jié)點(diǎn)插入到文檔中原來范圍所在的位置上
3、將文檔片段的內(nèi)容添加到給定節(jié)點(diǎn)中
可以使用這種技術(shù)來突出顯示網(wǎng)頁中的某些詞句,例如下列代碼
var p1 = document.getElementById("p1"); var helloNode = p1.firstChild.firstChild; var worldNode = p1.lastChild; var range = document.createRange(); range.selectNode(helloNode); var span = document.createElement("span"); span.style.backgroundColor = "yellow"; range.surroundContents(span);
以上代碼會給范圍選區(qū)加上一個黃色的背景。得到的HTML代碼如下所示
Hello world!
為了插入,范圍必須包含整個DOM選區(qū),而不能僅僅包含選中的DOM節(jié)點(diǎn)
折疊范圍
所謂折疊范圍,就是指范圍中未選擇文檔的任何部分。可以用文本框來描述折疊范圍的過程。假設(shè)文本框中有一行文本,用鼠標(biāo)選擇了其中一個完整的單詞。然后,單擊鼠標(biāo)左鍵,選區(qū)消失,而光標(biāo)則落在了其中兩個字母之間。同樣,在折疊范圍時,其位置會落在文檔中的兩個部分之間,可能是范圍選區(qū)的開始位置,也可能是結(jié)束位置。下圖展示了折疊范圍時發(fā)生的情形
【collapse()】
使用collapse()
方法來折疊范圍,這個方法接受一個參數(shù),該參數(shù)是一個布爾值,表示要折疊到范圍的哪一端。參數(shù)true表示折疊到范圍的起點(diǎn),參數(shù)false表示折疊到范圍的終點(diǎn)。要確定范圍已經(jīng)折疊完畢,可以檢査collapsed屬性,如下所示:
range.collapse(true);//折疊到起點(diǎn) console.log(range.collapsed);//輸出true
檢測某個范圍是否處于折疊狀態(tài),可以幫我們確定范圍中的兩個節(jié)點(diǎn)是否緊密相鄰。例如,對于下面的HTML代碼
Paragraph 1
Paragraph 2
如果不知道其實(shí)際構(gòu)成(比如說,這行代碼是動態(tài)生成的),那么可以像下面這樣創(chuàng)建一個范圍
var p1 = document.getElementById("p1"); var p2 = document.getElementById("p2"); var range = document.createRange(); range.setStartAfter(p1); range.setStartBefore(p2); console.log(range.collapsed);//輸出true
在這個例子中,新創(chuàng)建的范圍是折疊的,因?yàn)閜1的后面和p2的前面什么也沒有
比較范圍
【compareBoundaryPoints()】
在有多個范圍的情況下,可以使用compareBoundaryPoints()
方法來確定這些范圍是否有公共的邊界(起點(diǎn)或終點(diǎn))。這個方法接受兩個參數(shù):表示比較方式的常量值和要比較的范圍。表示比較方式的常量值如下所示
Range.START_TO_START(0)
:比較第一個范圍和第二個范圍的起點(diǎn)
Range.START_TO_END(1)
:比較第一個范圍的起點(diǎn)和第二個范圍的終點(diǎn)
Range.END_TO_END⑵
:比較第一個范圍和第二個范圍的終點(diǎn)
Range.END_T0_START(3)
:比較第一個范圍的終點(diǎn)和第一個范圍的起點(diǎn)
compareBoundaryPoints()
方法可能的返回值如下:如果第一個范圍中的點(diǎn)位于第二個范圍中的點(diǎn)之前,返回-1;如果兩個點(diǎn)相等,返回0;如果第一個范圍中的點(diǎn)位于第二個范圍中的點(diǎn)之后,返回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)); //outputs 0 alert(range1.compareBoundaryPoints(Range.END_TO_END, range2)); //outputs 1
在這個例子中,兩個范圍的起點(diǎn)實(shí)際上是相同的,因?yàn)樗鼈兊钠瘘c(diǎn)都是由selectNodeContents()
方法設(shè)置的默認(rèn)值來指定的。因此,第一次比較返回0。但是,range2的終點(diǎn)由于調(diào)用setEndBefore()
已經(jīng)改變了,結(jié)果是range1的終點(diǎn)位于range2的終點(diǎn)后面,因此第二次比較返回1
復(fù)制范圍
【cloneRange】
可以使用cloneRange()
方法復(fù)制范圍。這個方法會創(chuàng)建調(diào)用它的范圍的一個副本
var newRange = range.cloneRange();
新創(chuàng)建的范圍與原來的范圍包含相同的屬性,而修改它的端點(diǎn)不會影響原來的范圍
清理范圍
【detach()】
在使用完范圍之后,最好是調(diào)用detach()
方法,以便從創(chuàng)建范圍的文檔中分離出該范圍。調(diào)用detach()
之后,就可以放心地解除對范圍的引用,從而讓垃圾回收機(jī)制回收其內(nèi)存了。來看下面的例子
range.detach();//從文檔中分離 range = null;//解除引用
在使用范圍的最后再執(zhí)行這兩個步驟是推薦的方式。一且分離范圍,就不能再恢復(fù)使用了
IE兼容
IE8-瀏覽器不支持DOM范圍。但支持一種類似的概念,即文本范圍(textrange)。文本范圍是IE專有的特性,其他瀏覽器都不支持。顧名思義,文本范圍處理的主要是文本(不一定是DOM節(jié)點(diǎn))。通過
、