如何在 IE 的 contentEditable div 中获取选定的文本节点?

Posted

技术标签:

【中文标题】如何在 IE 的 contentEditable div 中获取选定的文本节点?【英文标题】:How to get selected textnode in contentEditable div in IE? 【发布时间】:2011-03-08 19:53:10 【问题描述】:

我正在尝试在 div 标签上创建一个带有 html5 contenteditable 的简单文本编辑器。如您所知,所选文本在 IE 中的处理方式完全不同。

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

我正在寻找一种方法来获取选定的文本节点(而不是文本本身),就像我可以在其他浏览器上使用“anchorNode”和“focusNode”一样。我在 IE 上发现的唯一替代方法是“parentElement()”函数,它只能选择 contenteditable div 本身。

有什么想法吗?

【问题讨论】:

【参考方案1】:

您目前最好的选择是IERange。该库将在 IE 中返回一个类似 DOM Range 的对象,并根据节点和偏移量提供选择。

【讨论】:

我已经查看了 IERange,但即使它有效,我也更喜欢使用原始 TextRange 对象(或至少不涉及全新库)的方法。还有其他想法吗? 没有。 IERange 从选择生成的TextRange 开始工作,并做了一些相当巧妙的工作来确定TextRange 的边界位于哪些节点。没有其他方法,只有在TextRange 上完成的精确工作的变化。此外,IERange 不是很大,您可以相当轻松地只提取您想要的位。【参考方案2】:

我想出了一个解决方案。它并不完美,但它能够选择包含文本节点的元素(最常见的是ph1 等)。我们可以使用 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);

如果有人有更好的解决方案,请分享。

【讨论】:

你不需要这样做来获取包含元素。例如,如果您想要包含选择开始的元素,您可以使用var textRange = document.selection.createRange(); textRange.collapse(true); var element = textRange.parentElement(); 在这种情况下是否返回段落或文本节点?我回家后试试看。 元素,而不是文本节点。有关获取文本节点的内容,请参阅我的新答案。【参考方案3】:

这是你需要的来自IERange 的函数版本,以及我的 cmets:

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'

【讨论】:

【参考方案4】:

我的答案来自 Tim Down 的解决方案。非常感谢蒂姆!

但是,在使用 IE 时,Tim 的解决方案存在两个问题。 (1) 计算出的偏移量不正确,(2) 太复杂,代码很多。

请看下面我的演示。

对于问题 1,如果您单击文本最后一行的某处,例如“肩部猪里脊肉 turducken shank 牛。培根球尖沙朗火腿”中的某处,您会注意到偏移量计算与IE(原始解决方案)和 IE 方法 2(我的解决方案)。另外,IE方法2(我的解决方案)和Chrome、Firefox的结果是一样的。

我的解决方案也简单得多。诀窍是,在使用 TextRange 在绝对 X/Y 位置进行选择之后,通过调用 document.getSelection() 获取 IHTMLSelection 类型。这不适用于 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://***.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://***.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) 
		  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) 
		  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>";
    

  // Firefox, Safari
  // REF: https://developer.mozilla.org/en-US/docs/Web/API/Document/caretPositionFromPoint
  if (document.caretPositionFromPoint) 
		  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) 
		  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>

【讨论】:

以上是关于如何在 IE 的 contentEditable div 中获取选定的文本节点?的主要内容,如果未能解决你的问题,请参考以下文章

禁用 IE 中带有“contentEditable”标志的元素的自动 URL 检测

contenteditable = false contenteditable = true块在IE8中仍然可以编辑

如何使用 contentEditable 和大小样式删除调整大小手柄和 div 边框

可编辑div contenteditable="plaintext-only" 模拟输入框,在IE浏览器下,鼠标不显示光标,不能输入编辑

将插入符号位置设置为始终以 contenteditable div 结尾 [重复]

实时监测contenteditable(可编辑文档)的内容发生改变