2010-06-27 2 views
3

Я пытаюсь создать простой текстовый редактор с HTML5 contenteditable в теге div. Как вы знаете, выделенный текст обрабатывается по-разному в IE.Как получить выбранный текстовый код в contentEditable div в IE?

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

Я ищу способ, чтобы получить выбранный TextNode (а не сам текст), как я могу сделать с «anchorNode» и «focusNode» на других браузерах. Единственной альтернативой IE, которую я нашел, является функция «parentElement()», которой только удается выбрать сам контент contenteditable.

Любые идеи?

ответ

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. Эта библиотека вернет вам объект DOM Range-like в IE с выбором, предоставляемым в терминах узлов и смещений.

+0

Я принял взглянуть на IERange, но даже если он работает, я предпочел бы способ сделать это с исходным объектом TextRange (или, по крайней мере, не вовлекая весь новый библиотека). Любая другая идея? – Herber

+0

№ IERange работает из '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 (истина); var element = textRange.parentElement(); '. –

+0

Возвращает ли этот абзац или текстовый индекс в этом случае? Я попробую, когда вернусь домой. – Herber

+0

Элемент, а не текстовый узел. См. Мой новый ответ на то, что получает текстовый узел. –

0

Мой ответ получен из раствора Тима». Большое спасибо Тиму!

Тем не менее, существует проблема с решением Тима при работе с IE. (1) неверное смещение, и (2) слишком сложное, много кода.

См. Мою демонстрацию ниже.

Для проблемы 1, если вы нажмете где-нибудь около последней строки текста, например, где-нибудь в «плечевой свиной корейке с коротким хвостом», вы можете заметить, что расчет смещения отличается с IE (оригинальное решение) и IE методом 2 (мое решение). Кроме того, результаты IE-метода 2 (мое решение) и из Chrome, Firefox одинаковы.

Мое решение также намного проще. Трюк заключается в том, что после использования TextRange для выбора в абсолютной позиции X/Y получить тип IHTMLSelection, вызывая document.getSelection(). Это не работает для 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>