如何防止在contenteditable div中将HTML单词粘贴到原始文本中间后,插入符跳到整个文本的末尾?

3

概述/背景

我正在尝试创建一个可编辑的 div,当它被粘贴 HTML 代码时,希望它能自动转换为纯文本,并且光标能够自动跳到粘贴的 HTML 代码的末尾。

我的尝试

我已经尝试过制作它了,以下是我编写的代码。

<html>

<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Test</title>
</head>

<body>
<script src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
<script>
function stripHTML(input){
        var output = '';
        if(typeof(input) == "string"){
                output = input.replace(/(<([^>]+)>)/ig, "");
        }
        return output;
}

$(function(){
        $("#text").focus(function(event){
                setItv = setInterval('clearHTML()', 1);
        });
        $("#text").blur(function(event){
                clearInterval(setItv);
        });
});

function clearHTML(){
        var patt = /(<([^>]+)>)/ig;
        var input = $("#text").html();

        if(input.search(patt) >= 0){
                var output = '';
                if(typeof(input) == "string"){
                        output = stripHTML(input);
                }
                $("#text").html(output);
        }
}
</script>

<div contenteditable="true" style="border: 1px #000 solid; width: 300px;
       height: 100px;" id="text">Hello, world!</div>

</body>

</html>

问题

最大的问题是光标位置。在将HTML粘贴在原始文本中间(例如,在“Hello”和“world”之间粘贴)之后,插入符会跳到整个文本的末尾,而不是粘贴的HTML的末尾。通常,当我们在原始文本的中间粘贴一小段单词时,插入符会跳到粘贴的单词的末尾,而不是整个文本的末尾。但在这种情况下,我不知道为什么它会自动跳到整个文本的末尾。

次要问题是setInterval函数。也许它不会引起任何问题,但脚本的方式非常不专业,可能会影响程序的效率。

问题

  1. 如何防止在contenteditable div中将HTML单词粘贴在原始文本的中间后插入符跳到整个文本的末尾?
  2. 如何优化编写脚本,而不使用setInterval函数
1个回答

2
作为起点,我建议进行以下改进:
  • 直接检查可编辑内容中的元素是否存在,而不是在innerHTML属性上使用正则表达式。这将提高性能,因为innerHTML速度较慢。您可以使用标准DOM方法 getElementsByTagName() 来实现。如果您喜欢使用jQuery,稍微效率低一些,您可以使用 $("#text").find("*")。更简单和更快的方法是检查您的可编辑元素是否有一个仅包含文本节点的子元素。
  • 通过用包含元素的textContent属性的文本节点替换元素的内容来剥离HTML。这比使用正则表达式替换HTML标记更可靠。
  • 在剥离HTML标记之前将选择内容存储为字符偏移量,并在之后恢复它。我以前在 Stack Overflow 上发布了代码来实现这一点。
  • 当相关事件触发时执行HTML剥离(我建议从keypress, keyup, inputpaste开始)。我会保持setInterval()使用更少频繁的时间间隔,以应对那些未被这些事件覆盖的情况。
以下代码段包含所有这些改进:

var saveSelection, restoreSelection;

if (window.getSelection && document.createRange) {
    saveSelection = function(containerEl) {
        var range = window.getSelection().getRangeAt(0);
        var preSelectionRange = range.cloneRange();
        preSelectionRange.selectNodeContents(containerEl);
        preSelectionRange.setEnd(range.startContainer, range.startOffset);
        var start = preSelectionRange.toString().length;

        return {
            start: start,
            end: start + range.toString().length
        };
    };

    restoreSelection = function(containerEl, savedSel) {
        var charIndex = 0, range = document.createRange();
        range.setStart(containerEl, 0);
        range.collapse(true);
        var nodeStack = [containerEl], node, foundStart = false, stop = false;

        while (!stop && (node = nodeStack.pop())) {
            if (node.nodeType == 3) {
                var nextCharIndex = charIndex + node.length;
                if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
                    range.setStart(node, savedSel.start - charIndex);
                    foundStart = true;
                }
                if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
                    range.setEnd(node, savedSel.end - charIndex);
                    stop = true;
                }
                charIndex = nextCharIndex;
            } else {
                var i = node.childNodes.length;
                while (i--) {
                    nodeStack.push(node.childNodes[i]);
                }
            }
        }

        var sel = window.getSelection();
        sel.removeAllRanges();
        sel.addRange(range);
    }
} else if (document.selection) {
    saveSelection = function(containerEl) {
        var selectedTextRange = document.selection.createRange();
        var preSelectionTextRange = document.body.createTextRange();
        preSelectionTextRange.moveToElementText(containerEl);
        preSelectionTextRange.setEndPoint("EndToStart", selectedTextRange);
        var start = preSelectionTextRange.text.length;

        return {
            start: start,
            end: start + selectedTextRange.text.length
        }
    };

    restoreSelection = function(containerEl, savedSel) {
        var textRange = document.body.createTextRange();
        textRange.moveToElementText(containerEl);
        textRange.collapse(true);
        textRange.moveEnd("character", savedSel.end);
        textRange.moveStart("character", savedSel.start);
        textRange.select();
    };
}

$(function(){
  var setInv;
  var $text = $("#text");

  $text.focus(function(event){
    setItv = setInterval(clearHTML, 200);
  });
  
  $text.blur(function(event){
    clearInterval(setItv);
  });
  
  $text.on("paste keypress keyup input", function() {
    // Allow a short delay so that paste and keypress events have completed their default action bewfore stripping HTML
    setTimeout(clearHTML, 1)
  });
});

function clearHTML() {
  var $el = $("#text");
  var el = $el[0];
  if (el.childNodes.length != 1 || el.firstChild.nodeType != 3 /* Text node*/) {
    var savedSel = saveSelection(el);
    $el.text( $el.text() );
    restoreSelection(el, savedSel);
  }
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div contenteditable="true" style="border: 1px #000 solid; width: 300px;
       height: 100px;" id="text">Hello, world!</div>


@RedWhale:这是一个相当不同和更加复杂的问题。您需要浏览可编辑元素的内容,并删除除<img>元素以外的元素,我会递归执行这个操作。否则,方法是一样的。 - Tim Down
非常感谢。但是还有一个关键问题没有回答... 有没有更好的方法来优化程序,而不使用setInterval函数或setTimeout函数? - Banana Code
@RedWhale:我在我的回答中提到了这一点。我认为你无法完全避免它们。例如,对于“粘贴”事件,你需要使用setTimeout(),因为它会在实际发生粘贴之前触发。 - Tim Down
我在IE中发现了另外两个问题:为什么在IE11中它不再剥离HTML标签?而在IE8中,插入包含<br>标签的HTML单词后,插入符跳到粘贴单词的最后2个字符。 - Banana Code
@RedWhale:在IE 11中,对我来说运行得非常好。在不支持与其他浏览器相同的选择和范围API的IE 8中,换行符可能会导致保存/恢复选择代码出现问题。绕过这个问题很复杂。 - Tim Down
显示剩余3条评论

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