2011-06-05 78 views
30

我想突出顯示(應用css)某個文本範圍,以其開始和結束位置表示。這比看起來更不明顯,因爲文本中可能有其他標籤,需要忽略。使用JavaScript突出顯示文本範圍

例:

<div>abcd<em>efg</em>hij</div> 

highlight(2, 6)需要突出"cdef「不刪除標記

我一直在使用一個TextRange對象試過了,但都沒有成功

提前感謝

+0

您可以剝離臨時字符串中的標籤,然後從該字符串中獲取子字符串嗎? – mc10 2011-06-05 00:28:29

+0

你不能忽略標籤,否則你會以不合法的html結尾:'ab cd ef f'。您需要執行以下操作:ab cd ef g' – serg 2011-06-05 00:37:10

+0

當然,我不能忽略這些標籤,但如果瀏覽器能夠以某種方式爲我解決這些問題,那將會很不錯。 – Vincent 2011-06-05 11:03:59

回答

48

下面是設置一個特定元素中選擇的一對字符偏移量的函數。這是天真的實現:它沒有考慮任何可能被隱藏的文本(例如,通過CSS或者通過在<script><style>元素中),並且可能存在瀏覽器差異(IE與其他所有內容)和換行符,並且不考慮摺疊的空白(例如,2個或多個連續的空格字符摺疊到頁面上的一個可見空間)。但是,它適用於所有主流瀏覽器中的示例。

對於其他部分,突出顯示,我建議使用document.execCommand()。您可以使用我的以下功能設置選項,然後致電document.execCommand()。您需要在非IE瀏覽器中臨時編輯文檔才能使該命令生效。見我的答案在這裏代碼:getSelection & surroundContents across multiple tags

這裏是展示整個事情的jsfiddle例如,在所有主要的瀏覽器中工作:http://jsfiddle.net/8mdX4/1211/

和選擇設置代碼:

function getTextNodesIn(node) { 
    var textNodes = []; 
    if (node.nodeType == 3) { 
     textNodes.push(node); 
    } else { 
     var children = node.childNodes; 
     for (var i = 0, len = children.length; i < len; ++i) { 
      textNodes.push.apply(textNodes, getTextNodesIn(children[i])); 
     } 
    } 
    return textNodes; 
} 

function setSelectionRange(el, start, end) { 
    if (document.createRange && window.getSelection) { 
     var range = document.createRange(); 
     range.selectNodeContents(el); 
     var textNodes = getTextNodesIn(el); 
     var foundStart = false; 
     var charCount = 0, endCharCount; 

     for (var i = 0, textNode; textNode = textNodes[i++];) { 
      endCharCount = charCount + textNode.length; 
      if (!foundStart && start >= charCount 
        && (start < endCharCount || 
        (start == endCharCount && i <= textNodes.length))) { 
       range.setStart(textNode, start - charCount); 
       foundStart = true; 
      } 
      if (foundStart && end <= endCharCount) { 
       range.setEnd(textNode, end - charCount); 
       break; 
      } 
      charCount = endCharCount; 
     } 

     var sel = window.getSelection(); 
     sel.removeAllRanges(); 
     sel.addRange(range); 
    } else if (document.selection && document.body.createTextRange) { 
     var textRange = document.body.createTextRange(); 
     textRange.moveToElementText(el); 
     textRange.collapse(true); 
     textRange.moveEnd("character", end); 
     textRange.moveStart("character", start); 
     textRange.select(); 
    } 
} 
+2

輝煌的解決方案@ tim-down!您的代碼適用於整合嵌套HTML格式的文本。 http://stackoverflow.com/questions/16226671/consolidate-stacked-dom-formatting-elements-contenteditable-div – 2013-04-26 20:59:38

+0

當我運行這段代碼時,span標籤被添加到所有突出顯示的文本中,我可以添加一個類並點擊事件的跨度標籤? – Prateek 2013-09-13 09:15:53

+0

@prateek:我認爲你的意思是突出顯示的代碼?由於跨度是由瀏覽器自動添加的,因此找到並修改它們並不容易。您可以使用我的[Rangy](https://code.google.com/p/rangy)庫的[class applier module](https://code.google.com/p/rangy/wiki/CSSClassApplierModule) 。 – 2013-09-13 09:23:37

0

以下解決方案不適用於IE,您需要應用TextRange對象等。這款採用選擇執行此,它不應該打破HTML在正常情況下,例如:

<div>abcd<span>efg</span>hij</div> 

隨着highlight(3,6);

輸出:

<div>abc<em>d<span>ef</span></em><span>g</span>hij</div> 

必須注意它是如何包裝第一跨越的字符變爲em,然後將其餘的span轉換爲新的字符。在那裏,好像它只是打開它在字符3和第6個字母結束,它會給合法的標記,如:

<div>abc<em>d<span>ef</em>g</span>hij</div> 

代碼:

var r = document.createRange(); 
var s = window.getSelection() 

r.selectNode($('div')[0]); 
s.removeAllRanges(); 
s.addRange(r); 

// not quite sure why firefox has problems with this 
if ($.browser.webkit) { 
    s.modify("move", "backward", "documentboundary"); 
} 

function highlight(start,end){ 
    for(var st=0;st<start;st++){ 
     s.modify("move", "forward", "character"); 
    } 

    for(var st=0;st<(end-start);st++){ 
     s.modify("extend", "forward", "character"); 
    } 
} 

highlight(2,6); 

var ra = s.getRangeAt(0); 
var newNode = document.createElement("em"); 
newNode.appendChild(ra.extractContents()); 
ra.insertNode(newNode); 

例子:http://jsfiddle.net/niklasvh/4NDb9/

編輯看起來像至少我的FF4有一些問題

s.modify("move", "backward", "documentboundary"); 

,但在同一時間,似乎沒有它的工作,所以我只是把它改爲

if ($.browser.webkit) { 
     s.modify("move", "backward", "documentboundary"); 
} 

編輯 蒂姆指出的那樣,修改只能從FF4起,所以我採取了不同的方法來獲得選擇,而不需要修改方法,希望能夠讓瀏覽器兼容一些(IE仍然需要它自己的解決方案)。

的代碼:

var r = document.createRange(); 
var s = window.getSelection() 

var pos = 0; 

function dig(el){ 
    $(el).contents().each(function(i,e){ 
     if (e.nodeType==1){ 
      // not a textnode 
     dig(e); 
     }else{ 
      if (pos<start){ 
       if (pos+e.length>=start){ 
       range.setStart(e, start-pos); 
       } 
      } 

      if (pos<end){ 
       if (pos+e.length>=end){ 
       range.setEnd(e, end-pos); 
       } 
      }    

      pos = pos+e.length; 
     } 
    }); 
} 
var start,end, range; 

function highlight(element,st,en){ 
    range = document.createRange(); 
    start = st; 
    end = en; 
    dig(element); 
    s.addRange(range); 

} 
highlight($('div'),3,6); 

var ra = s.getRangeAt(0); 

var newNode = document.createElement("em"); 
newNode.appendChild(ra.extractContents()); 
ra.insertNode(newNode); 

例如:http://jsfiddle.net/niklasvh/4NDb9/

+0

自Firefox 4.0以來,Firefox只實現'Selection.modify()',並且不支持WebKit所做的所有粒度設置。具體而言,它不支持「文檔邊界」。請參閱https://developer.mozilla.org/en/DOM/selection/modify – 2011-06-05 10:15:17

+0

這似乎只在頁面上沒有其他內容時才起作用。請參閱:http://jsfiddle.net/4NDb9/89/。儘管Hello world在div之外,但它被突出顯示,而不是div內的文本。 – Vincent 2011-06-05 11:02:45

+0

@Tim Down非常好的一點。我重寫了它的很大一部分來擺脫現在的modify()方法。 – Niklas 2011-06-05 11:05:42