DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> JavaScript入門知識 >> 關於JavaScript >> JS Range HTML文檔/文字內容選中、庫及應用介紹
JS Range HTML文檔/文字內容選中、庫及應用介紹
編輯:關於JavaScript     
一、前面的些話
本文的內容基本上是基於“區域范圍對象(Range objects)”這個概念來說的。這個玩意,可以讓你選擇HTML文檔的任意部分,並可以拿這些選擇的信息做你想做的事情。其中,最常見的Range是用戶用鼠標選擇的內容(user selection)。

本文有不少篇幅就是講如何將用戶的這種選擇轉換為W3C Range或Microsoft Text Range對象。

二、什麼是Range?
所謂"Range",是指HTML文檔中任意一段內容。一個Range的起始點和結束點位置任意,甚至起始點和結束點可以是一樣的(也就是空Range)。最常見的Range是用戶文本選擇范圍(user text selection)。當用戶選擇了頁面上的某一段文字後,你就可以把這個選擇轉為Range。當然,你也可以直接用程序定義Range。

例如下面這個模樣的例子:

2011-04-12 
負責調查切爾諾貝利核事故對人與環境造成影響的俄科學家亞布羅科夫博士指出,因福島核電站使用的燃料較切爾諾貝利核電站多,且有反應堆使用了含有高毒性的钚的燃料,因此"福島核電站事故可能會比切爾諾貝利帶來更嚴重的後果"。

上面選中狀態的那些文字就可以轉換成Range對象(下面會詳細講述)。通過Range對象你可以找到Range的起始點和結束點,如果你實在有心,還可以刪除或是復制這些內容,或是用其他文字替換,甚至是簡單的HTML。

上面的例子可以說是最簡單的Range對象的例子,因為其只包含了文字。而實際上,Range對象也是可以包含HTML代碼內容的,例如下面這個示例:

<time>2011-04-12</time> 
<p>據日本廣播協會電視台12日報道,日本經濟產業省原子能安全保安院決定將福島第一核電站核洩漏事故等級提高至7級。這使日本核洩漏事故等級與蘇聯切爾諾貝利核電站核洩漏事故等級相同。</p> 
<p>負責調查切爾諾貝利核事故對人與環境造成影響的俄科學家亞布羅科夫博士指出,因福島核電站使用的燃料較切爾諾貝利核電站多,且有反應堆使用了含有高毒性的钚的燃料,因此"福島核電站事故可能會比切爾諾貝利帶來更嚴重的後果"。</p>

同樣的,Range對象被創建,且包含HTML,現在的問題是選擇的內容正好跨過了楚河和漢界(跨標簽),如果就單純的論選擇的內容的話,應該如下:

洩漏事故等級與蘇聯切爾諾貝利核電站核洩漏事故等級相同。</p> 
<p>負責調查切爾諾貝

顯然,上面的HTML屬於1級殘廢,基本無效。然而幸運的是,所有的浏覽器都會自動調整HTML片段使其有效,就像變成下面這樣:

<p>洩漏事故等級與蘇聯切爾諾貝利核電站核洩漏事故等級相同。</p> 
<p>負責調查切爾諾貝</p>

可以看到,浏覽器自動補全了一定數目的HTML來讓Range有效。如果你復制或是移動Range,你所復制或移動的HTML內容一定是有效的。

如果您看到下面的文字,可能是由於在其他網站或是RSS中閱讀本文,本文原地址:http://www.zhangxinxu.com/wordpress/?p=1591,本文作者:張鑫旭,來自張鑫旭-鑫空間-鑫生活,訪問原出處更多優秀技術文章。

三、浏覽器的兼容性

在真正操刀JavaScript之前我們需要大致知道Range對象的浏覽器兼容性情況。實際上,問題是比較麻煩的,因為至少有3種類似Range對象,且你有必要全部理解。先展示詳細的兼容性情況表:

支持:支持不支持:不支持部分支持:部分支持

1. W3C Range
W3C Range
  Explorer 6/7 Firefox 2 Safari 1.3 Opera 9 cloneContents() 不支持 支持 部分支持 支持 cloneRange() 不支持 支持 支持 支持 collapse() tbd tbd tbd tbd collapsed 不支持 支持 支持 支持 commonAncestorContainer 不支持 支持 支持 支持 compareBoundaryPoints() 不支持 支持 支持 支持 comparePoint() – Mozilla 擴展 不支持 支持 不支持 不支持 createContextualFragment() – Mozilla 擴展 不支持 支持 支持 支持 deleteContents() 不支持 支持 支持 支持 detach() 不支持 支持 支持 支持 endContainer 不支持 支持 支持 支持 endOffset 不支持 支持 支持 部分支持 extractContents() 不支持 支持 支持 支持 insertNode() 不支持 支持 支持 支持 isPointInRange() – Mozilla 擴展 不支持 支持 不支持 不支持 selectNode() 不支持 支持 支持 支持 selectNodeContents() 不支持 支持 支持 支持 setEnd() 不支持 支持 支持 支持 setEndAfter() 不支持 支持 支持 支持 setEndBefore() 不支持 支持 支持 支持 setStart() 不支持 支持 支持 支持 setStartAfter() 不支持 支持 支持 支持 setStartBefore() 不支持 支持 支持 支持 startContainer 不支持 支持 支持 支持 startOffset 不支持 支持 支持 部分支持 surroundContents() 不支持 支持 支持 支持

說明:
cloneContents()的用法類似docFrag = rangeObject.cloneContents()Range對象內容被克隆同時被添加到文檔片段上,並返回自身。但是在Safari下有個問題,即如果選擇范圍是空,將會返回null而不是空的文檔片段。可以通過類似docFrag = rangeObject.cloneContents() || document.createDocumentFragment()這樣的代碼修復。

deleteContents()處,Range內容會被永久刪除,無返回值。

endContainer指用戶選擇內容結尾處的容器節點。通常是文本節點。

extractContents()用法docFrag = rangeObject.extractContents()。從DOM樹上剪切Range對象並返回文檔片段。該片段可以粘貼到頁面上。

startContainer指用戶選擇內容起始處的容器節點。通常是文本節點。

startOffset在Opera浏覽器下,在選擇內容為空的時候返回0

2. Mozilla Selection
Mozilla Selection
  Explorer 6/7 Firefox 2 Safari 1.3 Opera 9 addRange() 不支持 支持 不支持 支持 anchorNode 不支持 支持 支持 支持 anchorOffset 不支持 支持 支持 部分支持 collapse() tbd tbd tbd tbd collapseToEnd() 不支持 支持 支持 支持 collapseToStart() 不支持 支持 支持 支持 containsNode() 不支持 支持 不支持 支持 deleteFromDocument() 不支持 支持 不支持 支持 extend() 不支持 支持 不支持 支持 focusNode 不支持 支持 支持 支持 focusOffset 不支持 支持 支持 部分支持 getRangeAt() 不支持 支持 不支持 支持 isCollapsed 不支持 支持 支持 支持 rangeCount 不支持 支持 不支持 支持 removeAllRanges() 不支持 支持 不支持 支持 removeRange() 不支持 支持 不支持 支持 selectAllChildren() 不支持 支持 不支持 支持 selectionLanguageChange() 不支持 支持 不支持 支持

說明:
anchorNode用法為userSelection.anchorNode。指用戶選擇內容起始處的容器節點。通常是文本節點。

anchorNode在Opera浏覽器下,在選擇內容為空的時候返回0

focusNode用法為userSelection.focusNode。指用戶選擇內容結尾處的容器節點。通常是文本節點。

focusOffset在Opera浏覽器下,在選擇內容為空的時候返回0

getRangeAt()用法為rangeObject = userSelection.getRangeAt(0),作用是將Mozilla Selection轉換為W3C Range

3. Microsoft TextRange
Microsoft TextRange
  Explorer 6/7 Firefox 2 Safari 1.3 Opera 9 boundingHeight 支持 不支持 不支持 不支持 boundingLeft 支持 不支持 不支持 不支持 boundingTop 支持 不支持 不支持 不支持 boundingWidth 支持 不支持 不支持 不支持 collapse() tbd tbd tbd tbd compareEndPoints() 支持 不支持 不支持 不支持 duplicate() 支持 不支持 不支持 支持 expand() 支持 不支持 不支持 不支持 findText() 支持 不支持 不支持 不支持 htmlText 支持 不支持 不支持 不支持 move() 支持 不支持 不支持 支持 moveEnd() 支持 不支持 不支持 支持 moveStart() 支持 不支持 不支持 支持 moveToElementText() 支持 不支持 不支持 支持 moveToPoint() 支持 不支持 不支持 不支持 offsetLeft 支持 不支持 不支持 不支持 offsetTop 支持 不支持 不支持 不支持 parentElement() 支持 不支持 不支持 支持 pasteHTML() 支持 不支持 不支持 不支持 scrollIntoView() 支持 不支持 不支持 不支持 select() 支持 不支持 不支持 支持 text 支持 不支持 不支持 支持

說明:
htmlText用法為htmlString = userSelection.htmlText。返回字符串,為TextRange的HTML內容,相當於innerHTML。只讀。

pasteHTML(),當粘貼HTML到一個文本節點時,該文本節點自動分隔。

text用法為string = userSelection.text。返回字符串,為TextRange的文本內容,相當於innerText。可讀/寫。

4. 總的兼容性
總的兼容性
  Explorer 6/7 Firefox 2 Safari 1.3 Opera 9 W3C Range

詳述

不支持 支持 支持 支持 Mozilla Selection

詳述

不支持 支持 部分支持 支持 Microsoft Text Range

詳述

支持 不支持 不支持 部分支持

說明:

  • W3C Range對象是唯一官方指定。基本上其是將Range作為包含DOM的文檔片段。
  • Mozilla Selection對象顯得有些多余,其存在是為了向後兼容Netscape 4。其類似於W3C Range對象,也是基於DOM樹的。
  • Microsoft Text Range對象跟上面兩個就是郭德綱和玄彬的區別了,因為其是基於字符串的。事實上,Text Range包含的字符串是很難一下子跳變成DOM節點的。

總的來說,Mozilla Selection對象就是個打醬油的命,僅有的閃光點能夠直接將用戶選擇任何內容變成完全Range對象以及一些額外的方法或是屬性可以向後兼容Netscape 4。但是不幸的是除了IE浏覽器外的其他浏覽器都支持此Selection對象

四、獲得用戶選擇內容

婆婆媽媽的解釋就免了,直接看相關代碼:
復制代碼 代碼如下:
var userSelection;
if (window.getSelection) { //現代浏覽器
userSelection = window.getSelection();
} else if (document.selection) { //IE浏覽器 考慮到Opera,應該放在後面
userSelection = document.selection.createRange();
}

由於兼容性的問題,IE浏覽器吃IE的包子,其他浏覽器吃Mozilla的饅頭。

在Mozilla、Safari、Opera下上面的userSelection是個Selection對象,而在IE下則是Text Range對象。這種差異會影響到你後面的腳本:Internet Explorer的Text Ranges完全不同於Mozilla的Selection或是W3C的Range對象,你需要分別為IE和其他浏覽器寫兩套不同的腳本。

需要注意腳本書寫的順序:Mozilla Selection需放在前面。原因在於Opera支持兩種對象,如果你使用window.getSelection()去讀取用戶選擇的內容,Opera會創建一個Selection對象;而使用document.selection則會創建一個Text Range對象。

因為Opera支持Mozilla Selection和W3C Range非常好,但是其對Microsoft Text Range的支持卻差強人意。所以顯然優先考慮標准浏覽器,即使用window.getSelection()。

五、userSelection的內容
userSelection變量現在的內容要麼是Mozilla Selection要麼就是Microsoft Text Range對象。因此它允許訪問定義在對象上的全部方法和屬性。

Mozilla Selection對象包含用戶選擇的文本內容,如下操作:

alert(userSelection)
雖然格式並不是字符串,但是在現代浏覽器下還是會彈出類似下面的內容:

洩漏事故等級與蘇聯切爾諾貝利核電站核洩漏事故等級相同。負責調查切爾諾貝
為了從Microsoft Text Range 對象上獲得同樣的信息,你需要使用userSelection.text。為了讀取旋轉的文字,可以使用類似下面代碼:
復制代碼 代碼如下:
var selectedText = userSelection;
if (userSelection.text) {
selectedText = userSelection.text;
}

現在selectedText就包含了用戶所選中的文字了。

您可以狠狠地點擊這裡:獲取用戶所選文字demo

例如在IE7浏覽器下,選中一段文字再點擊demo頁面上的測試按鈕,就會有類似下面的彈出內容:

六、從Selection對象創建Range對象
在IE浏覽器下,userSelection是Text Range,在現代浏覽器下,userSelection仍然是Selection對象,要想同樣創建和Selection對象內容一樣的Range對象可以使用類似下面代碼:
復制代碼 代碼如下:
var getRangeObject = function(selectionObject) {
if (selectionObject.getRangeAt)
return selectionObject.getRangeAt(0);
else { // 較老版本Safari!
var range = document.createRange();
range.setStart(selectionObject.anchorNode,selectionObject.anchorOffset);
range.setEnd(selectionObject.focusNode,selectionObject.focusOffset);
return range;
}
}
var rangeObject = getRangeObject(userSelection);

理想情況下,我們通過Selection對象的getRangeAt()方法就可以得到W3C Range對象。此方法可以返回給定索引值的range對象。通常情況下,在JavaScript中第一個Range的索引值是0。

使用程序創建Range

Safari 1.3不支持getRangeAt(),因此我們要想兼顧此浏覽器,需要使用其他的方法創建新的Range對象。顯然,顯示創建一個對象:

var range = document.createRange();
上面的一行代碼創建了一個空的Range,為了插入內容,我們需要通過setStart()和setEnd()方法定義起止點。

這兩個方法需要兩個參數:
1. Range起止的DOM節點
2. Range起止的文本偏移。該偏移指選中文字第一個字符和最後一個字符在文本節點中的位置。

setStart()的兩個參數屬性為startContainer和startOffset;setEnd()兩個參數屬性為endContainer和endOffset。

以下面這個例子舉例:

<p>男人,即使到了50歲,也千萬不要碰超過26歲還沒有結婚的女人。她可以是離婚,喪偶等等的,但是絕對不能是沒有結婚。超過了26歲沒有結婚,這種女人一般心理變態,不然就是有嚴重問題。市場很少犯錯。即使它犯了錯,那被你撿到寶的概率也很小。</p>
<p>婚姻市場未來的變化將會是很有趣的問題,而且對未來大陸經濟的走勢也有舉足輕重的影響,對於行業的分布,經濟的整體效率有決定性的影響。</p>

<ol>
<li>為什麼是26這個准確的數字?</li>
<li>找罵帖</li>
<li>言論是對的,在100年前,lz穿越了而已。</li>
</ol>此處Range開始於第二個<p>節點,結束與第一個<li>節點。(通常文本節點的第一個字符的索引是0。)

由於<p>節點處的文字偏移值是8, <li>節點處的偏移是5,因而有:
復制代碼 代碼如下:
var startP = [the p node];
var endLi = [the second li node];
range.setStart(startP, 8);
range.setEnd(endLi, 5);

讀取起止選中內容

上面提到了setStart(startContainer, startOffset)以及setEnd(endContainer, endOffset)。考慮到實際情況,你很難准確知道用戶選擇的文字的起始位置,所以,上面一板一眼賦予偏移值的方法顯然有很大的局限性。好在任何(看參見上面的兼容性表格)Range對象有4個屬性是用來定義選擇內容起止點的,這4個屬性與Selection對象相似,但是卻是不同的名稱:anchorNode/anchorOffset定義選擇的起始,focusNode/focusOffset定義結束。

因此,上面的腳本創建選區可以使用如下代碼實現:

range.setStart(selectionObject.anchorNode,selectionObject.anchorOffset);
range.setEnd(selectionObject.focusNode,selectionObject.focusOffset);

Safari的多慮

現在已經是2011年了,釋小龍都有绯聞了,Safari 5已經出來好些日子了。所以,如果僅僅是為了兼顧低版本的Safari而去使用程序創建Range,我覺得是一點必要都沒有。尤其在我們這個神奇的國度上,首先使用Safari就少,低版本的就少之又少,Safari老早就已經支持getRangeAt()了,Chrome浏覽器也是如此。

您可以狠狠地點擊這裡:Safari下getRangeAt測試demo

選擇demo頁面中的任意一部分文字,然後點擊測試按鈕,在較新版本的Safari浏覽器下就會出現類似下圖的結果:

所以,在當前環境下,要想將Selection對象轉換成Range對象,直接如下代碼就OK了(完整版):
復制代碼 代碼如下:
var userSelection, rangeObject;
if (window.getSelection) {
//現代浏覽器
userSelection = window.getSelection();
} else if (document.selection) {
//IE浏覽器 考慮到Opera,應該放在後面
userSelection = document.selection.createRange();
}

//Range對象
rangeObject = userSelection;
if (userSelection.getRangeAt) {
//現代浏覽器
rangeObject = userSelection.getRangeAt(0);
}

七、rangy – JavaScript Range&Selection庫
項目地址:http://code.google.com/p/rangy/

就在幾天前,rangy更新到了版本1.1,作者還新更新了四五個示意的頁面,展示了相關的API,方法和屬性等。雖然如此,由於實例較少,還是讓人很難知道此JavaScript庫如何使用。這裡就舉幾個簡單的例子示意下。//zxx:此插件非壓縮達115K,個人覺得有些龐大,在實際項目中的應用價值不大

示例1,獲取用戶選中的文字:

您可以狠狠地點擊這裡:rangy獲取用戶選中文字demo

選中部分文字點擊按鈕,會有如下彈出(截自Firefox3.6):

相關JavaScript代碼如下:
復制代碼 代碼如下:
var sel = rangy.getSelection();
alert(sel.toString());

示例2,給選中文字添加背景

您可以狠狠地點擊這裡:文字選中添加背景圖demo

選中頁面上一段文字,然後失去焦點,就會看到文字後面有了個美女背景圖,如下截圖,截自IE7浏覽器:

完整JavaScript代碼如下:
復制代碼 代碼如下:
<script type="text/javascript" src="http://www.zhangxinxu.com/study/201104/rangy/rangy-core.js"></script>
<script type="text/javascript" src="http://www.zhangxinxu.com/study/201104/rangy/rangy-cssclassapplier.js"></script>
<script>
var cssApplier;
window.onload = function() {
rangy.init();
cssApplier = rangy.createCssClassApplier("selectClass", true);
document.body.onmouseup = function() {
cssApplier.toggleSelection();
};
};
</script>

如果您看到下面的文字,可能是由於在其他網站或是RSS中閱讀本文,本文原地址:http://www.zhangxinxu.com/wordpress/?p=1591,本文作者:張鑫旭,來自張鑫旭-鑫空間-鑫生活,訪問原出處更多優秀技術文章。
八、實際的應用
微博之插入話題

差不多去年這個時候,自己折騰過JS 文本域光標處添加文字並選中的內容,也是拿的新浪微博示例的,文章是“新浪微博插入話題後部分文字選中的js實現”,但是去年這篇文章多實現的話題插入效果是比較弱的:
1. 選中普通文字不能作為話題插入
2. 話題只能插在文本域最後二不是光標處
3. 默認文字的話題可以重復插入

所以,趁這個機會,正好把微博之插入話題這個功能完善下。

您可以狠狠地點擊這裡:微博插入話題的效果實現demo

歡迎輸入內容,點擊測試。大致會有類似下面的效果(截自Chrome):

源代碼有些高度,為了節約篇幅,這裡就不展示出來了,您可以在demo頁面中看到完整的CSS/HTML/JS代碼。不過JS部分半封裝,您要是有興趣可以在外面包裹一個函數使其插件化,我是懶得再去折騰了。

九、結語相關

對於Range相關的知識即使到現在都是半生不熟的,所以文章的內容更多的算是翻譯性質的內容。自己並沒有從深入理解的基礎上很淺顯地剖析相關知識點,文章很多地方會顯得不怎麼通俗易懂。

文中多展示的Range等兼容性表格的數據都是N年前的,還是Safari 1.3時代的數據,老的牙都掉了,實用價值大打折扣,不過可以告知的是先前現代浏覽器所不支持的個別屬性現早就支持了。

跌跌撞撞,滾滾爬爬。文章難免有表述不准確的地方,歡迎指正。也歡迎提交相關的腳本的bug。

參考文章及相關頁面:
  1. Introduction to Range
  2. W3C DOM Compatibility – Range
  3. rangy – A cross-browser JavaScript range and selection library
  4. Reveal a Background Image upon Text Selection

原創文章,轉載請注明來自張鑫旭-鑫空間-鑫生活

XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved