【问题标题】:How to prevent the caret jumping to the end of the whole text in contenteditable div after pasting the HTML words in the middle of the original text?将HTML文字粘贴到原文中间后,如何防止插入符号跳转到contenteditable div中的整个文本的末尾?
【发布时间】:2015-05-17 07:46:41
【问题描述】:

摘要/背景

我正在尝试制作一个内容可编辑的 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 的末尾。通常,当我们在原始文本的中间粘贴一个 sn-p 单词时,插入符号会跳转到粘贴单词的末尾,而不是整个文本的末尾。但是在这种情况下我不知道为什么它会自动跳转到整个文本的末尾。

第二个问题是 setInterval 函数。也许不会造成任何问题,但是脚本编写的方式非常不专业,可能会影响程序的效率。

问题

  1. 在原文中间粘贴HTML文字后,如何防止插入符跳转到contenteditable div中整个文本的末尾?
  2. 如何在不使用 setInterval 函数的情况下优化脚本?

【问题讨论】:

    标签: javascript jquery html contenteditable


    【解决方案1】:

    作为起点,我建议进行以下改进:

    • 直接检查可编辑内容中是否存在元素,而不是在innerHTML 属性上使用正则表达式。这将提高性能,因为innerHTML 很慢。您可以使用标准 DOM 方法 getElementsByTagName() 来完成此操作。如果您更喜欢使用效率稍低的 jQuery,您可以使用$("#text").find("*")。更简单、更快捷的是检查您的可编辑元素是否有一个作为文本节点的子节点。
    • 通过将元素的内容替换为包含元素textContent 属性的文本节点来剥离HTML。这比用正则表达式替换 HTML 标记更可靠。
    • 在剥离 HTML 标记之前将所选内容存储为字符偏移量,然后将其恢复。我有previously posted code on Stack Overflow to do this
    • 当相关事件触发时执行 HTML 剥离(我建议 keypresskeyupinputpaste 用于初学者)。我会保留setInterval() 的间隔时间较短,以处理这些事件未涵盖的其他情况

    以下 sn-p 包含所有这些改进:

    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>

    【讨论】:

    • 对不起,如果我现在允许图像(只有 标签)出现在 contenteditable div 中,我应该如何修改代码?
    • @RedWhale:这是一个完全不同且更复杂的问题。您需要浏览可编辑元素的内容并删除除&lt;img&gt; 元素之外的元素,我会递归地执行此操作。否则方法是一样的。
    • 非常感谢。但是还是有一个症结没有回答……有没有更好的方法来优化程序而不使用setInterval函数或者setTimeout函数?
    • @RedWhale:我在回答中提到了这一点。我认为你不能完全避免它们。例如,paste 事件需要 setTimeout(),因为它会在粘贴实际发生之前触发。
    • 我在 IE 中发现了另外两个问题:为什么在 IE11 中它不再去除 HTML 标签?在 IE 8 中,插入包含 &lt;br&gt; 标记的 HTML 单词后,插入符号会跳转到粘贴单词的后 2 个字符。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-25
    • 2012-05-29
    • 1970-01-01
    • 2011-05-13
    • 1970-01-01
    相关资源
    最近更新 更多