ContentEditable 内的 Non-Editable Span 选择和删除问题 DIV

Selection and deletion problems with Non-Editable Span inside ContentEditable DIV

我有一个 DIV ContentEditable 设置为 TRUE。其中有几个 ContentEditable 设置为 FALSE 的 span。

我捕获 BackSpace 键,这样如果光标下的元素是 <span> 我可以删除它。

问题是它只能与奇数跨度交替工作。

因此,例如,对于下面的 html 代码,将光标放在 DIV 中文本的末尾,然后一直按退格键直到 div 的开头.观察它会 select/delete 第一个跨度,然后离开第二个,然后 select/delete 第三个跨度,然后离开第四个,依此类推。

此行为仅适用于 Internet Explorer。它在 Firefox 上完全符合预期。

我应该如何使行为在 Internet Explorer 中保持一致?

以下 html 代码可用于重现该行为:

var EditableDiv = document.getElementById('EditableDiv');

EditableDiv.onkeydown = function(event) {
  var ignoreKey;
  var key = event.keyCode || event.charCode;
  if (!window.getSelection) return;
  var selection = window.getSelection();
  var focusNode = selection.focusNode,
    anchorNode = selection.anchorNode;

  if (key == 8) { //backspace
    if (!selection.isCollapsed) {
      if (focusNode.nodeName == 'SPAN' || anchorNode.nodeName == 'SPAN') {
        anchorNode.parentNode.removeChild(anchorNode);
        ignoreKey = true;
      }
    } else if (anchorNode.previousSibling && anchorNode.previousSibling.nodeName == 'SPAN' && selection.anchorOffset <= 1) {
      SelectText(event, anchorNode.previousSibling);
      ignoreKey = true;
    }
  }
  if (ignoreKey) {
    var evt = event || window.event;
    if (evt.stopPropagation) evt.stopPropagation();
    evt.preventDefault();
    return false;
  }
}

function SelectText(event, element) {
  var range, selection;
  EditableDiv.focus();
  if (document.body.createTextRange && element.nodeName == 'SPAN') {
    range = document.body.createTextRange();
    range.moveToElementText(element);
    range.select();
  } else if (window.getSelection) {
    selection = window.getSelection();
    range = document.createRange();
    range.selectNodeContents(element);
    selection.removeAllRanges();
    selection.addRange(range);
  }
  var evt = (event) ? event : window.event;
  if (evt.stopPropagation) evt.stopPropagation();
  if (evt.cancelBubble != null) evt.cancelBubble = true;
  return false;
}
#EditableDiv {
  height: 75px;
  width: 500px;
  font-family: Consolas;
  font-size: 10pt;
  font-weight: normal;
  letter-spacing: 1px;
  background-color: white;
  overflow-y: scroll;
  overflow-x: hidden;
  border: 1px solid black;
  padding: 5px;
}
#EditableDiv span {
  color: brown;
  font-family: Verdana;
  font-size: 8.5pt;
  min-width: 10px;
  _width: 10px;
}
#EditableDiv p,
#EditableDiv br {
  display: inline;
}
<div id="EditableDiv" contenteditable="true">
  &nbsp;(<span contenteditable='false' onclick='SelectText(event, this);' unselectable='on'>Field1</span> < 500) <span contenteditable='false' onclick='SelectText(event, this);' unselectable='on'>OR</span> (<span contenteditable='false' onclick='SelectText(event, this);' unselectable='on'>Field2</span> > 100 <span contenteditable='false' onclick='SelectText(event, this);' unselectable='on'>AND</span> (<span contenteditable='false' onclick='SelectText(event, this);'
    unselectable='on'>Field3</span> <=200) ) 
</div>

编辑

仅供参考。我也在 MSDN Forum 中问过这个问题。

这方面的挑战是让 IE11 从右边直接向后space 。然后下一个backspace会select高亮显示。这看起来很简单objective,但是IE11就是不配合。应该有一个快速简单的补丁,对吧?所以错误开始了。

我想出的方法是将树向后移动到第一个先前的非空节点,清除之间的空节点以安抚 IE,然后评估一个很少的条件。如果插入符 应该 结束在 的右侧,那么通过使用 select 创建一个新的范围对象来手动执行(因为 IE 不会)在 .

online demo


的末尾离子两个跨度相互拖动的情况。例如,Field2Field3。当您然后从右侧返回space 到 Field3,然后再次返回space 删除它时,IE 会将插入符号向左跳到 Field2 上。直接跳过 Field2。呃。解决方法是拦截它并在一对跨度之间插入一个 space 。我不相信你会对此感到满意。但是,你知道,这是一种解决方法。无论如何,这又出现了另一个错误,IE 将插入的 space 更改为两个空文本节点。更多grrr。因此,解决方法的解决方法。请参阅非 isCollapsed 代码。

代码片段

var EditableDiv = document.getElementById('EditableDiv');

       EditableDiv.onkeydown = function(event) {
         var ignoreKey;
         var key = event.keyCode || event.charCode;
         if (!window.getSelection) return;
         var selection = window.getSelection();
         var focusNode = selection.focusNode,
           anchorNode = selection.anchorNode;

         var anchorOffset = selection.anchorOffset;

         if (!anchorNode) return

         if (anchorNode.nodeName.toLowerCase() != '#text') {
           if (anchorOffset < anchorNode.childNodes.length)
             anchorNode = anchorNode.childNodes[anchorOffset]
           else {
             while (!anchorNode.nextSibling) anchorNode = anchorNode.parentNode // this might step out of EditableDiv to "justincase" comment node
             anchorNode = anchorNode.nextSibling
           }
           anchorOffset = 0
         }

         function backseek() {

           while ((anchorOffset == 0) && (anchorNode != EditableDiv)) {

             if (anchorNode.previousSibling) {
               if (anchorNode.previousSibling.nodeName.toLowerCase() == '#text') {
                 if (anchorNode.previousSibling.nodeValue.length == 0)
                   anchorNode.parentNode.removeChild(anchorNode.previousSibling)
                 else {
                   anchorNode = anchorNode.previousSibling
                   anchorOffset = anchorNode.nodeValue.length
                 }
               } else if ((anchorNode.previousSibling.offsetWidth == 0) && (anchorNode.previousSibling.offsetHeight == 0))
                 anchorNode.parentNode.removeChild(anchorNode.previousSibling)

               else {
                 anchorNode = anchorNode.previousSibling

                 while ((anchorNode.lastChild) && (anchorNode.nodeName.toUpperCase() != 'SPAN')) {

                   if ((anchorNode.lastChild.offsetWidth == 0) && (anchorNode.lastChild.offsetHeight == 0))
                     anchorNode.removeChild(anchorNode.lastChild)

                   else if (anchorNode.lastChild.nodeName.toLowerCase() != '#text')
                     anchorNode = anchorNode.lastChild

                   else if (anchorNode.lastChild.nodeValue.length == 0)
                     anchorNode.removeChild(anchorNode.lastChild)

                   else {
                     anchorNode = anchorNode.lastChild
                     anchorOffset = anchorNode.nodeValue.length
                       //break       //don't need to break, textnode has no children
                   }
                 }
                 break
               }
             } else
               while (((anchorNode = anchorNode.parentNode) != EditableDiv) && !anchorNode.previousSibling) {}
           }
         }

         if (key == 8) { //backspace
           if (!selection.isCollapsed) {

             try {
               document.createElement("select").size = -1
             } catch (e) { //kludge for IE when 2+ SPANs are back-to-back adjacent

               if (anchorNode.nodeName.toUpperCase() == 'SPAN') {
                 backseek()
                 if (anchorNode.nodeName.toUpperCase() == 'SPAN') {
                   var k = document.createTextNode(" ") // doesn't work here between two spans.  IE makes TWO EMPTY textnodes instead !
                   anchorNode.parentNode.insertBefore(k, anchorNode) // this works
                   anchorNode.parentNode.insertBefore(anchorNode, k) // simulate "insertAfter"
                 }
               }
             }


           } else {
             backseek()

             if (anchorNode == EditableDiv)
               ignoreKey = true

             else if (anchorNode.nodeName.toUpperCase() == 'SPAN') {
               SelectText(event, anchorNode)
               ignoreKey = true
             } else if ((anchorNode.nodeName.toLowerCase() == '#text') && (anchorOffset <= 1)) {

               var prev, anchorNodeSave = anchorNode,
                 anchorOffsetSave = anchorOffset
               anchorOffset = 0
               backseek()
               if (anchorNode.nodeName.toUpperCase() == 'SPAN') prev = anchorNode
               anchorNode = anchorNodeSave
               anchorOffset = anchorOffsetSave

               if (prev) {
                 if (anchorOffset == 0)
                   SelectEvent(prev)

                 else {
                   var r = document.createRange()
                   selection.removeAllRanges()

                   if (anchorNode.nodeValue.length > 1) {
                     r.setStart(anchorNode, 0)
                     selection.addRange(r)
                     anchorNode.deleteData(0, 1) 
                   } 
                   else {
                     for (var i = 0, p = prev.parentNode; true; i++)
                       if (p.childNodes[i] == prev) break
                     r.setStart(p, ++i)
                     selection.addRange(r)
                     anchorNode.parentNode.removeChild(anchorNode)
                   }
                 }
                 ignoreKey = true
               }
             }
           }
         }
         if (ignoreKey) {
           var evt = event || window.event;
           if (evt.stopPropagation) evt.stopPropagation();
           evt.preventDefault();
           return false;
         }
       }

       function SelectText(event, element) {
         var range, selection;
         EditableDiv.focus();
         if (window.getSelection) {
           selection = window.getSelection();
           range = document.createRange();
           range.selectNode(element)
           selection.removeAllRanges();
           selection.addRange(range);
         } else {
           range = document.body.createTextRange();
           range.moveToElementText(element);
           range.select();
         }
         var evt = (event) ? event : window.event;
         if (evt.stopPropagation) evt.stopPropagation();
         if (evt.cancelBubble != null) evt.cancelBubble = true;
         return false;
       }
#EditableDiv {
          height: 75px;
          width: 500px;
          font-family: Consolas;
          font-size: 10pt;
          font-weight: normal;
          letter-spacing: 1px;
          background-color: white;
          overflow-y: scroll;
          overflow-x: hidden;
          border: 1px solid black;
          padding: 5px;
        }
        #EditableDiv span {
          color: brown;
          font-family: Verdana;
          font-size: 8.5pt;
          min-width: 10px;
          /*_width: 10px;*/
          /* what is this? */
        }
        #EditableDiv p,
        #EditableDiv br {
          display: inline;
        }
<div id="EditableDiv" contenteditable="true">
&nbsp;(<span contenteditable='false' onclick='SelectText(event, this);' unselectable='on'>Field1</span> < 500)  <span contenteditable='false' onclick='SelectText(event, this);' unselectable='on'>OR</span> (<span contenteditable='false' onclick='SelectText(event, this);' unselectable='on'>Field2</span> > 100 <span contenteditable='false' onclick='SelectText(event, this);' unselectable='on'>AND</span> (<span contenteditable='false' onclick='SelectText(event, this);' unselectable='on'>Field3</span> <= 200) )
</div>