跨浏览器插入BR或P标签的方法,当在contentEditable元素上按Enter时。

21

在可编辑的contentEditable元素上按回车键时,每个浏览器处理生成的代码方式都不同:Firefox插入BR标签,Chrome插入DIV标签,而Internet Explorer插入P标签

我曾经拼命寻找至少在所有浏览器中使用BR或P的解决方案,最常见的答案是:

插入BR标签:

$("#editableElement").on("keypress", function(e){
      if (e.which == 13) {
        if (window.getSelection) {
          var selection = window.getSelection(),
              range = selection.getRangeAt(0),
              br = document.createElement("br");
          range.deleteContents();
          range.insertNode(br);
          range.setStartAfter(br);
          range.setEndAfter(br);
          selection.removeAllRanges();
          selection.addRange(range);
          return false;
        }
      }
    });

但是这样做行不通,因为浏览器似乎不知道如何在 <br> 后设置插入符号,这意味着以下内容没有任何实用价值(特别是如果你在文本末尾放置插入符号时按下回车键):

range.setStartAfter(br);
range.setEndAfter(br);

有些人会说:使用双重 <br><br>,但这样在文本节点中按回车键会导致两个换行符。

其他人会说,始终在 contentEditable 的末尾添加额外的 <br>,但是如果你有一个 <div contenteditable><p>text here</p></div> 并将光标放置在文本末尾然后按回车键,则会出现错误的行为。

所以我想也许我们可以使用 P 代替 BR,常见的答案是:

插入 P 标签:

document.execCommand('formatBlock', false, 'p');

但是这也不能始终奏效。

正如您所见,所有这些解决方案都有些缺陷。是否有另一种解决方案可以解决这个问题?


1
我理解你的感受...我也面临着同样烦人的问题 :) - Swag
2个回答

11

一种可能的解决方案:在 <br> 元素后附加一个文本节点,其中包含一个零宽度空格字符。这是一个不可打印的零宽度字符,专门设计用于:

... 当使用不具有显式间距的脚本或在字符(例如斜线)之后但其后可能仍有换行符时,指示文本处理系统单词边界。

(维基百科)

已在 Chrome 48、Firefox 43 和 IE11 中测试。

$("#editableElement").on("keypress", function(e) {
  //if the last character is a zero-width space, remove it
  var contentEditableHTML = $("#editableElement").html();
  var lastCharCode = contentEditableHTML.charCodeAt(contentEditableHTML.length - 1);
  if (lastCharCode == 8203) {
    $("#editableElement").html(contentEditableHTML.slice(0, -1));
  }
  // handle "Enter" keypress
  if (e.which == 13) {
    if (window.getSelection) {
      var selection = window.getSelection();
      var range = selection.getRangeAt(0);
      var br = document.createElement("br");
      var zwsp = document.createTextNode("\u200B");
      var textNodeParent = document.getSelection().anchorNode.parentNode;
      var inSpan = textNodeParent.nodeName == "SPAN";
      var span = document.createElement("span");
      
      // if the carat is inside a <span>, move it out of the <span> tag
      if (inSpan) {
        range.setStartAfter(textNodeParent);
        range.setEndAfter(textNodeParent);
      }

      // insert the <br>
      range.deleteContents();
      range.insertNode(br);
      range.setStartAfter(br);
      range.setEndAfter(br);
      
      // create a new span on the next line
      if (inSpan) {
        range.insertNode(span);
        range.setStart(span, 0);
        range.setEnd(span, 0);
      }

      // add a zero-width character
      range.insertNode(zwsp);
      range.setStartBefore(zwsp);
      range.setEndBefore(zwsp);
      
      // insert the new range
      selection.removeAllRanges();
      selection.addRange(range);
      return false;
    }
  }
});
#editableElement {
  height: 150px;
  width: 500px;
  border: 1px solid black;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div contenteditable=true id="editableElement">
  <span>sample text</span>
</div>


非常酷。到目前为止,我使用的方法几乎相同,但是使用空格代替零宽不连字符。当然,这需要检查注入的<br>是否是父级contenteditable中最后一个非空节点,否则可见空格将遍布整个页面。这样做要好得多。 - Igor Raush
谢谢您的回答,有没有办法在每次按下回车键时删除先前添加的 ‌? - medBouzid
当然可以,但是你需要等到另一个字符被放置在“TextNode”中,否则你会回到最初的状态(光标相对于空的“TextNode”)。我在“keypress”处理程序的开头放置了一些代码,用于检查最后一个字符是否为“zwsp”并将其删除。 - rphv
请注意,这不会处理您打破现有文本行的情况 - 对于此,您将不得不实施检查以查看是否正在开始新行,可能使用此处概述的策略(https://dev59.com/-ms05IYBdhLWcg3wGuGd#7473638)。 - rphv
1
@rphv,你真是太棒了,你给了我一个更好的想法来删除那些字符 :) 当我完成它时,我可能会发布代码,只有一件事,如果插入符在span元素内,我按回车键,你知道如何关闭span标签并在其后添加br,然后打开新的span标签吗?这是我需要的唯一部分,非常感谢。 - medBouzid
@medBo - 我已经编辑了上面的代码片段。您可以检查光标(插入符)是否在<span>元素中,方法如下: document.getSelection().anchorNode.parentNode.nodeName == "SPAN"。然后将光标移出<span>,插入换行符<br>标记,并为下一行创建一个新的<span> - rphv

1
你可以在这里看到完整的跨浏览器实现。有很多技巧可以让它工作。来自链接的代码会帮助你设计解决方案。
Geko和IE hack的示例:
doc.createElement( 'br' ).insertAfter( startBlock );

// A text node is required by Gecko only to make the cursor blink.
if ( CKEDITOR.env.gecko )
    doc.createText( '' ).insertAfter( startBlock );

// IE has different behaviors regarding position.
range.setStartAt( startBlock.getNext(), 
    CKEDITOR.env.ie ? CKEDITOR.POSITION_BEFORE_START :
        CKEDITOR.POSITION_AFTER_START );

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接