2010-06-27 46 views
3

我正在嘗試在div標籤上創建一個帶HTML5 contenteditable的簡單文本編輯器。如您所知,在IE中處理選定的文本的方式會大不相同。如何在IE的contentEditable div中獲取選定的textnode?

this.retrieveAnchorNode = function() { 
     var anchorNode; 
     if (document.selection) 
     anchorNode = document.selection.createRange().parentElement(); 
     else if (document.getSelection) 
     anchorNode = window.getSelection().anchorNode; 
    } 

我正在尋找一種方式來獲得所選擇的textnode(而不是文本自身),就像我可以用其他瀏覽器上「anchorNode」和「focusNode」做。我唯一能找到的替代方案是「parentElement()」函數,它只能設置自己選擇的內容。

任何想法?

回答

3

這裏是我的版本,從IERange需要的功能,有我的評語:

function getChildIndex(node) { 
    var i = 0; 
    while((node = node.previousSibling)) { 
    i++; 
    } 
    return i; 
} 

function getTextRangeBoundaryPosition(textRange, isStart) { 
    var workingRange = textRange.duplicate(); 
    workingRange.collapse(isStart); 
    var containerElement = workingRange.parentElement(); 
    var workingNode = document.createElement("span"); 
    var comparison, workingComparisonType = isStart ? 
    "StartToStart" : "StartToEnd"; 

    var boundaryPosition, boundaryNode; 

    // Move the working range through the container's children, starting at 
    // the end and working backwards, until the working range reaches or goes 
    // past the boundary we're interested in 
    do { 
    containerElement.insertBefore(workingNode, workingNode.previousSibling); 
    workingRange.moveToElementText(workingNode); 
    } while ((comparison = workingRange.compareEndPoints(
    workingComparisonType, textRange)) > 0 && workingNode.previousSibling); 

    // We've now reached or gone past the boundary of the text range we're 
    // interested in so have identified the node we want 
    boundaryNode = workingNode.nextSibling; 
    if (comparison == -1 && boundaryNode) { 
    // This must be a data node (text, comment, cdata) since we've overshot. 
    // The working range is collapsed at the start of the node containing 
    // the text range's boundary, so we move the end of the working range 
    // to the boundary point and measure the length of its text to get 
    // the boundary's offset within the node 
    workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange); 

    boundaryPosition = { 
     node: boundaryNode, 
     offset: workingRange.text.length 
    }; 
    } else { 
    // We've hit the boundary exactly, so this must be an element 
    boundaryPosition = { 
     node: containerElement, 
     offset: getChildIndex(workingNode) 
    }; 
    } 

    // Clean up 
    workingNode.parentNode.removeChild(workingNode); 

    return boundaryPosition; 
} 

var textRange = document.selection.createRange(); 
var selectionStart = getTextRangeBoundaryPosition(textRange, true); 
// selectionStart has properties 'node' and 'offset' 
+0

感謝您的修復! – Herber 2010-07-01 10:39:22

2

目前最好的選擇是IERange。這個庫會在IE中返回一個DOM Range-like對象,並根據節點和偏移量提供選擇。

+0

我已經採取了看看IERange,但即使它的工作原理我寧願方式與原來的TextRange對象做到這一點(或至少不涉及一個全新的圖書館)。 有沒有其他想法? – Herber 2010-06-27 21:07:08

+0

編號IERange從選擇中生成的'TextRange'工作,並做了一些非常狡猾的工作,以確定'TextRange'的邊界位於哪個節點。沒有其他方法,只有在' TextRange'。此外,IERange不是很大,你可以合理地輕鬆提取你想要的位。 – 2010-06-27 21:46:51

0

我想出了一個解決方案。這並不完美,但它能夠選擇包含文本節點的元素(最常見的是p,h1等)。我們可以通過TextRange函數getBoundingClientRect()獲得選擇的位置,使用document.elementFromPoint(x, y)我們可以獲取包含文本的元素。例如:

var textRange = document.selection.createRange(); 
var x = textRange.getBoundingClientRect().left; 
var y = textRange.getBoundingClientRect().top; 
var element = document.elementFromPoint(x, y); 

如果有人有更好的解決方案,請分享。

+0

您不需要執行此操作即可獲取包含的元素。例如,如果你想要包含選擇開始的元素,你可以執行'var textRange = document.selection.createRange(); textRange.collapse(真); var element = textRange.parentElement();'。 – 2010-06-29 13:15:19

+0

在這種情況下是否返回段落或文本節點?當我回家時我會嘗試。 – Herber 2010-06-29 13:35:15

+0

元素,而不是文本節點。查看我的新答案獲取文本節點的內容。 – 2010-06-29 14:46:29

0

我的答案是由蒂姆向下」的解決方案的。非常感謝蒂姆!

但是,在使用IE時,Tim的解決方案有兩個問題。 (1)計算的偏移量不正確,(2)太複雜,代碼太多。

請參閱下面的演示。

對於問題1,如果你點擊某個關於文本的最後一行,例如在某處「肩豬裏脊克爾turducken柄牛。培根球尖牛腩火腿」,你可以看到偏移計算是不同的與IE(原始解決方案)和IE方法2(我的解決方案)。另外,來自IE方法2(我的解決方案)和Chrome,Firefox的結果是一樣的。

我的解決方案也簡單得多。技巧是,在使用TextRange在絕對X/Y位置進行選擇之後,通過調用document.getSelection()來獲取一個IHTMLSelection類型。這不適用於IE < 9,但如果這對你來說沒問題,這種方法就簡單多了。另一個需要注意的是,在IE中,該方法的副作用(與原始方法相同)是選擇的改變(即失去了用戶的原始選擇)。

// Internet Explorer method 2 
    if (document.body.createTextRange) { 
      elt.innerHTML = elt.innerHTML+("*************** IE, Method 2 **************<br/>"); 
     range = document.body.createTextRange(); 
     range.moveToPoint(event.clientX, event.clientY); 
     range.select(); 
     var sel = document.getSelection(); 
     textNode = sel.anchorNode; 
     offset = sel.anchorOffset; 
     elt.innerHTML = elt.innerHTML + "IE M2 ok, result: [" + escapeHtml(textNode.nodeName) + "]/[" + escapeHtml(textNode.textContent) + "] @" + offset + "</br>"; 
    } 

function escapeHtml(unsafe) { 
 
    return unsafe 
 
    .replace(/&/g, "&amp;") 
 
    .replace(/</g, "&lt;") 
 
    .replace(/>/g, "&gt;") 
 
    .replace(/"/g, "&quot;") 
 
    .replace(/'/g, "&#039;"); 
 
} 
 

 
// REF: http://stackoverflow.com/questions/3127369/how-to-get-selected-textnode-in-contenteditable-div-in-ie 
 
function getChildIndex(node) { 
 
    var i = 0; 
 
    while((node = node.previousSibling)) { 
 
    i++; 
 
    } 
 
    return i; 
 
} 
 

 
// All this code just to make this work with IE, OTL 
 
// REF: http://stackoverflow.com/questions/3127369/how-to-get-selected-textnode-in-contenteditable-div-in-ie 
 
function getTextRangeBoundaryPosition(textRange, isStart) { 
 
    var workingRange = textRange.duplicate(); 
 
    workingRange.collapse(isStart); 
 
    var containerElement = workingRange.parentElement(); 
 
    var workingNode = document.createElement("span"); 
 
    var comparison, workingComparisonType = isStart ? 
 
    "StartToStart" : "StartToEnd"; 
 

 
    var boundaryPosition, boundaryNode; 
 

 
    // Move the working range through the container's children, starting at 
 
    // the end and working backwards, until the working range reaches or goes 
 
    // past the boundary we're interested in 
 
    do { 
 
    containerElement.insertBefore(workingNode, workingNode.previousSibling); 
 
    workingRange.moveToElementText(workingNode); 
 
    } while ((comparison = workingRange.compareEndPoints(
 
    workingComparisonType, textRange)) > 0 && workingNode.previousSibling); 
 

 
    // We've now reached or gone past the boundary of the text range we're 
 
    // interested in so have identified the node we want 
 
    boundaryNode = workingNode.nextSibling; 
 
    if (comparison == -1 && boundaryNode) { 
 
    // This must be a data node (text, comment, cdata) since we've overshot. 
 
    // The working range is collapsed at the start of the node containing 
 
    // the text range's boundary, so we move the end of the working range 
 
    // to the boundary point and measure the length of its text to get 
 
    // the boundary's offset within the node 
 
    workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange); 
 

 
    boundaryPosition = { 
 
     node: boundaryNode, 
 
     offset: workingRange.text.length 
 
    }; 
 
    } else { 
 
    // We've hit the boundary exactly, so this must be an element 
 
    boundaryPosition = { 
 
     node: containerElement, 
 
     offset: getChildIndex(workingNode) 
 
    }; 
 
    } 
 

 
    // Clean up 
 
    workingNode.parentNode.removeChild(workingNode); 
 

 
    return boundaryPosition; 
 
} 
 

 
function onClick(event) { 
 
    var elt = document.getElementById('info'); 
 
    elt.innerHTML = ""; 
 
    var textNode; 
 
    var offset; 
 
    // Internet Explorer 
 
    if (document.body.createTextRange) { 
 
\t \t elt.innerHTML = elt.innerHTML+("*************** IE **************<br/>"); 
 
     range = document.body.createTextRange(); 
 
     range.moveToPoint(event.clientX, event.clientY); 
 
     range.select(); 
 
     range = getTextRangeBoundaryPosition(range, true); 
 

 
     textNode = range.node; 
 
     offset = range.offset; 
 
     elt.innerHTML = elt.innerHTML + "IE ok, result: [" + escapeHtml(textNode.nodeName) + "]/[" + escapeHtml(textNode.textContent) + "] @" + offset + "</br>"; 
 

 
    } 
 
    
 
    // Internet Explorer method 2 
 
    if (document.body.createTextRange) { 
 
\t \t elt.innerHTML = elt.innerHTML+("*************** IE, Method 2 **************<br/>"); 
 
     range = document.body.createTextRange(); 
 
     range.moveToPoint(event.clientX, event.clientY); 
 
     range.select(); 
 
\t \t \t var sel = document.getSelection(); 
 
     textNode = sel.anchorNode; 
 
     offset = sel.anchorOffset; 
 
     elt.innerHTML = elt.innerHTML + "IE M2 ok, result: [" + escapeHtml(textNode.nodeName) + "]/[" + escapeHtml(textNode.textContent) + "] @" + offset + "</br>"; 
 
    } 
 

 
    // Firefox, Safari 
 
    // REF: https://developer.mozilla.org/en-US/docs/Web/API/Document/caretPositionFromPoint 
 
    if (document.caretPositionFromPoint) { 
 
\t \t elt.innerHTML = elt.innerHTML+("*************** Firefox, Safari **************<br/>"); 
 
    range = document.caretPositionFromPoint(event.clientX, event.clientY); 
 
    textNode = range.offsetNode; 
 
    offset = range.offset; 
 
    elt.innerHTML = elt.innerHTML + "caretPositionFromPoint ok, result: [" + escapeHtml(textNode.nodeName) + "]/[" + escapeHtml(textNode.textContent) + "] @" + offset + "</br>"; 
 
    // Chrome 
 
    // REF: https://developer.mozilla.org/en-US/docs/Web/API/document/caretRangeFromPoint 
 
    } 
 
    if (document.caretRangeFromPoint) { 
 
\t \t elt.innerHTML = elt.innerHTML+("*************** Chrome **************<br/>"); 
 
    range = document.caretRangeFromPoint(event.clientX, event.clientY); 
 
    textNode = range.startContainer; 
 
    offset = range.startOffset; 
 
    elt.innerHTML = elt.innerHTML + "caretRangeFromPoint ok, result: [" + escapeHtml(textNode.nodeName) + "]/[" + escapeHtml(textNode.textContent) + "] @" + offset + "</br>"; 
 
    } 
 
} 
 

 
document.addEventListener('click', onClick);
#info { 
 
    position: absolute; 
 
    bottom: 0; 
 
    background-color: cyan; 
 
}
<div class="parent"> 
 
    <div class="child">SPACE&nbsp;SPACE Bacon ipsum dolor amet <span>SPAN SPANTT SPOOR</span> meatball bresaola t-bone tri-tip brisket. Jowl pig picanha cupim SPAXE landjaeger, frankfurter spare ribs chicken. Porchetta jowl pancetta drumstick shankle cow spare ribs jerky 
 
    tail kevin biltong capicola brisket venison bresaola. Flank sirloin jowl andouille meatball venison salami ground round rump boudin turkey capicola t-bone. Sirloin filet mignon tenderloin beef, biltong doner bresaola brisket shoulder pork loin shankle 
 
    turducken shank cow. Bacon ball tip sirloin ham. 
 
    </div> 
 
    <div id="info">Click somewhere in the paragraph above</div> 
 
</div>

相關問題