【问题标题】:Selection and deletion problems with Non-Editable Span inside ContentEditable DIVContentEditable DIV 中的 Non-Editable Span 的选择和删除问题
【发布时间】:2015-10-02 04:50:20
【问题描述】:

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

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

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

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

此行为仅适用于 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 中问过这个问题。

【问题讨论】:

  • @user4749485,非常感谢您的努力。尝试了演示。如果您从最后一个开始退格,但从中间的某个位置开始时从右侧删除字符,则效果很好。你能调查一下吗。我相信可以解决。 :)
  • 你有没有提到stackoverflow.com/questions/14615551/… .. 请告诉我
  • @JafferWilson,是的,我已经搜索了很多关于这个影响你发布的链接的内容。它没有帮助。你有什么具体要我调查的吗?
  • @user4749485,我看不出你之前的演示和新的演示有什么区别!光标位置后的内容仍然被删除。例如将光标放在100 之前并开始退格。请注意,一旦Field2 被删除,100 也会被删除。
  • 我试图深入研究这个问题,结果在这里:MSDN Forums Thread。这可能会有所帮助。

标签: html internet-explorer contenteditable


【解决方案1】:

对此的挑战是让 IE11 直接从右侧退格到 。然后下一个退格键将选择并突出显示它。这似乎是一个如此简单的目标,但 IE11 就是不会配合。应该有一个快速简单的补丁,对吧?于是错误就开始了。

我想出的方法是将树倒退到前一个非空节点,清除其间的空节点以安抚 IE,然后评估一些条件。如果插入符号 应该 最终位于 的右侧,则手动执行此操作(因为 IE 不会),方法是创建一个新的范围 obj,并在 .

online demo


我为 IE 添加了一个额外的 kludge,以防两个跨度相互拖动。例如,Field2Field3。当您从右侧退格到 Field3,然后再次退格以删除它时,IE 会将插入符号向左跳过 Field2。直接跳过 Field2。呸。解决方法是拦截它并在这对跨度之间插入一个空格。我不相信你会对此感到满意。但是,你知道,这是一种解决方法。无论如何,这又出现了另一个错误,IE 将插入的空间更改为两个空的文本节点。更多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>

【讨论】:

  • ++ 还添加了另外 100 分作为赏金 :) 好答案@user4749485。不过只能在接下来的 23 小时内授予它:)
  • 此代码是否适用于移动设备。我已经在 Android chrome mobile 中尝试过,但这在那里不起作用。尝试使用流星 js 构建这种类型的应用程序。有没有办法在移动设备上也能做到这一点。
  • 你是个传奇
  • 我看不到SelectEvent函数定义!!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-07-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-24
相关资源
最近更新 更多