2010-06-27 4 views
3

div 태그에 HTML5 contenteditable이있는 간단한 텍스트 편집기를 만들려고합니다. 아시다시피, 선택한 텍스트는 IE에서 완전히 다르게 처리됩니다.IE에서 contentEditable div에 선택된 text 노드를 얻는 방법은 무엇입니까?

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

나는 다른 브라우저의 "anchorNode"와 "focusNode"로 할 수있는 것처럼, (하지 텍스트 자체) 선택한 textnode을 얻을 수있는 방법을 찾고 있어요. IE에서 유일한 대안은 "parentElement()"함수인데,이 함수는 contenteditable div 자체를 선택하기 만합니다.

아이디어가 있으십니까?

답변

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

2

현재로서는 가장 좋은 방법은 IERange입니다. 이 라이브러리는 IE에서 노드 범위와 유사한 객체를 반환합니다. 노드와 오프셋의 관점에서 선택이 제공됩니다.

+0

나는 IERange에서 모습을 촬영했지만, 그것이 작동하는 경우에도 나는 원래 TextRange 객체와 함께 할 수있는 방법을 선호하는 (또는 적어도 새로운 전체를 포함하지 도서관). 다른 아이디어가 있습니까? – Herber

+0

아니요. IERange는 선택에서 생성 된'TextRange'에서 작동하고'TextRange'의 경계가 놓여있는 노드를 확립하기 위해 아주 교활한 작업을합니다. 다른 접근 방식은 없습니다.'TextRange' TextRange'.게다가 IERange는 그리 크지 않고 원하는 비트 만 추출 할 수 있습니다. –

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 (true); var 요소 = textRange.parentElement();'. –

+0

이 경우 단락 또는 textnode를 반환합니까? 내가 집에 갈 때 나는 그것을 시험 할 것이다. – Herber

+0

요소이며 텍스트 노드는 아닙니다. 텍스트 노드를 가져 오는 것에 대한 새로운 대답을 참조하십시오. –

0

내 대답은 팀 다운 '솔루션에서 파생됩니다. 팀에게 고마워!

그러나 IE에서 작업 할 때 Tim의 솔루션에는 두 가지 문제가 있습니다. (1) 계산 된 오프셋이 부정확하고 (2) 코드가 너무 복잡합니다.

아래의 데모를 참조하십시오.

문제 1의 경우 텍스트의 마지막 줄을 어딘가에서 클릭하면 예를 들어 "어깨 뼈 고기 허리 고기 튀김 생크 돼지 .Bacon 볼 팁 등심 햄"의 어딘가에서 오프셋 계산이 다름을 알 수 있습니다 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>

관련 문제