例如下面這個模樣的例子:
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內容一定是有效的。
在真正操刀JavaScript之前我們需要大致知道Range對象
的浏覽器兼容性情況。實際上,問題是比較麻煩的,因為至少有3種類似Range對象
,且你有必要全部理解。先展示詳細的兼容性情況表:
支持:不支持:部分支持:
說明: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
。
說明:anchorNode
用法為userSelection.anchorNode
。指用戶選擇內容起始處的容器節點。通常是文本節點。
anchorNode
在Opera浏覽器下,在選擇內容為空的時候返回0
。
focusNode
用法為userSelection.focusNode
。指用戶選擇內容結尾處的容器節點。通常是文本節點。
focusOffset
在Opera浏覽器下,在選擇內容為空的時候返回0
。
getRangeAt()
用法為rangeObject = userSelection.getRangeAt(0),作用是將
Mozilla Selection
轉換為W3C Range
。
說明:htmlText
用法為htmlString = userSelection.htmlText
。返回字符串,為TextRange
的HTML內容,相當於innerHTML
。只讀。
pasteHTML()
,當粘貼HTML到一個文本節點時,該文本節點自動分隔。
text
用法為string = userSelection.text
。返回字符串,為TextRange
的文本內容,相當於innerText
。可讀/寫。
詳述
詳述
詳述
說明:
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。
參考文章及相關頁面:原創文章,轉載請注明來自張鑫旭-鑫空間-鑫生活