JavaScript技巧:在execCommand中使用“粘贴为纯文本”

140

我有一个基于 execCommand 的基本编辑器,遵循这里介绍的示例。在 execCommand 区域内有三种方法可以粘贴文本:

  • Ctrl+V
  • 右键单击 -> 粘贴
  • 右键单击 -> 粘贴为纯文本

我希望只允许粘贴没有任何 HTML 标记的纯文本。如何强制前两个操作粘贴纯文本?

可能的解决方案:我能想到的方法是设置 (Ctrl+V) 的 keyup 事件监听器,并在粘贴之前去除 HTML 标记。

  1. 这是最好的解决方案吗?
  2. 它是否足以避免粘贴任何 HTML 标记?
  3. 如何添加右键单击-> 粘贴的监听器?

5
顺便提一下,你也想处理拖入编辑器的文本吗?这是HTML泄漏到编辑器中的另一种方式。 - pimvdb
1
@pimvdb,你的回答已经满足了我的需求。只是出于好奇,有没有一种简单的方法来避免拖动泄漏呢? - Googlebot
2
我以为这个可以完成任务:http://jsfiddle.net/HBEzc/2/。但是至少在Chrome上,不幸的是,文本总是插入到编辑器的开头。 - pimvdb
你需要按照这里的说明使用剪贴板API。https://www.youtube.com/watch?v=Q81HH2Od5oo - Johne Doe
13个回答

301

这段代码将拦截 paste 事件、取消 paste 并手动插入剪贴板的文本内容:
http://jsfiddle.net/HBEzc/。 这应该是最可靠的方法:

  • 它可以捕获所有粘贴方式(Ctrl+V,上下文菜单等)
  • 它允许直接获取剪贴板数据作为文本,因此您不必使用丑陋的 hack 来替换 HTML。

虽然我不确定跨浏览器的支持情况。

editor.addEventListener("paste", function(e) {
    // cancel paste
    e.preventDefault();

    // get text representation of clipboard
    var text = (e.originalEvent || e).clipboardData.getData('text/plain');

    // insert text manually
    document.execCommand("insertHTML", false, text);
});

4
@Ali: 我错过了一个显而易见的东西。如果 text 包含 HTML(例如,如果你将 HTML 代码复制为纯文本),那么它实际上会将其粘贴为 HTML。 这里有一个解决方案,但它不是很美观:http://jsfiddle.net/HBEzc/3/. - pimvdb
14
var text = (event.originalEvent || event).clipboardData.getData('text/plain'); 这段代码可以提供更广泛的浏览器兼容性。请注意,我只提供翻译,不包括解释或其他内容。 - Duncan Walker
11
这会破坏撤销功能。(Ctrl+Z) - Rudey
5
很棒的解决方案,但这与默认行为有所不同。如果用户复制了类似于 <div></div> 的内容,该内容将作为可编辑元素的子元素添加。我通过以下方式进行修复:document.execCommand("insertText", false, text); - Jason Newell
6
我发现在IE11中,insertHTMLinsertText无法正常工作,但是document.execCommand('paste', false, text);可以正常使用。然而这种方法似乎在其他浏览器中不起作用。 >_> - Jamie Barker
显示剩余14条评论

49

我无法让这里被接受的答案在IE浏览器中工作,所以我进行了一些搜索并找到了这个答案,在IE11和最新版本的Chrome和Firefox中都可以正常工作。

$('[contenteditable]').on('paste', function(e) {
    e.preventDefault();
    var text = '';
    if (e.clipboardData || e.originalEvent.clipboardData) {
      text = (e.originalEvent || e).clipboardData.getData('text/plain');
    } else if (window.clipboardData) {
      text = window.clipboardData.getData('Text');
    }
    if (document.queryCommandSupported('insertText')) {
      document.execCommand('insertText', false, text);
    } else {
      document.execCommand('paste', false, text);
    }
});

1
谢谢,我也遇到了同样的问题……无论是 IE11 还是最新的 Firefox 都无法使用 insertText :) - HeberLZ
1
有可能在某些情况下它会将文本复制粘贴到Firefox和Chrome两个浏览器中吗?在我看来.. - Fanky
1
@Fanky,我在创建它时没有遇到这个问题,但是我不再在创建此代码的地方工作,所以我无法告诉您它是否仍然有效!您能描述一下它为什么会粘贴两次吗? - Jamie Barker
2
@Fanky 请看看能否在这里重新创建它:https://jsfiddle.net/v2qbp829/. - Jamie Barker
2
似乎我的问题是由于从一个被另一个脚本加载的文件中调用你的脚本所导致的。在 Firefox 47.0.1 中,我无法将内容粘贴到你的 fiddle 的 textarea 或 input 中(但可以在 Chrome 中),但可以将内容粘贴到可编辑的 div 中,这对我很重要。谢谢! - Fanky
显示剩余8条评论

23

和pimvdb提供的解决方案很接近。但它适用于FF、Chrome和IE 9:

editor.addEventListener("paste", function(e) {
    e.preventDefault();

    if (e.clipboardData) {
        content = (e.originalEvent || e).clipboardData.getData('text/plain');

        document.execCommand('insertText', false, content);
    }
    else if (window.clipboardData) {
        content = window.clipboardData.getData('Text');

        document.selection.createRange().pasteHTML(content);
    }   
});

5
我喜欢短路 content 变量赋值的方式。我发现使用 getData('Text') 能够在跨浏览器的情况下正常工作,所以你可以像这样只赋值一次:var content = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('Text'); 然后你只需要针对跨浏览器的粘贴/插入命令使用逻辑即可。 - gfullam
6
我认为你不能写document.selection.createRange().pasteHTML(content)... 我刚在IE11上测试了一下,它不会像那样工作。 - vsync
3
截至IE11和Edge版本,document.execCommand('insertText', false, content) 不起作用。同时,最新版本的Chrome支持更简单的 document.execCommand('paste', false, content)。他们可能会弃用 insertText 命令。 - Cannicide

19

当然,这个问题已经有答案并且话题很陈旧,但我想提供我的解决方案,因为它是简单而干净的:

这是在我的可编辑div上的粘贴事件内部。

var text = '';
var that = $(this);

if (e.clipboardData)
    text = e.clipboardData.getData('text/plain');
else if (window.clipboardData)
    text = window.clipboardData.getData('Text');
else if (e.originalEvent.clipboardData)
    text = $('<div></div>').text(e.originalEvent.clipboardData.getData('text'));

if (document.queryCommandSupported('insertText')) {
    document.execCommand('insertHTML', false, $(text).html());
    return false;
}
else { // IE > 7
    that.find('*').each(function () {
         $(this).addClass('within');
    });

    setTimeout(function () {
          // nochmal alle durchlaufen
          that.find('*').each(function () {
               // wenn das element keine klasse 'within' hat, dann unwrap
               // http://api.jquery.com/unwrap/
               $(this).not('.within').contents().unwrap();
          });
    }, 1);
}

else部分来自另一个我无法找到的SO帖子...


更新于2014年11月19日: 另一篇SO帖子


2
我认为你指的是这篇帖子:https://dev59.com/vWEi5IYBdhLWcg3wd8EF - gfullam
1
在Safari中似乎对我不起作用。也许有什么问题。 - Cannicide

9

所有已发布的答案似乎都不能跨浏览器正常工作,或者解决方案过于复杂:

  • insertText 命令不受 IE 支持。
  • 在 IE11 中使用 paste 命令会导致堆栈溢出错误。

以下方法适用于我(IE11、Edge、Chrome 和 FF):

$("div[contenteditable=true]").off('paste').on('paste', function(e) {
    e.preventDefault();
    var text = e.originalEvent.clipboardData ? e.originalEvent.clipboardData.getData('text/plain') : window.clipboardData.getData('Text');
    _insertText(text);
});

function _insertText(text) { 
    // use insertText command if supported
    if (document.queryCommandSupported('insertText')) {
        document.execCommand('insertText', false, text);
    }
    // or insert the text content at the caret's current position
    // replacing eventually selected content
    else {
        var range = document.getSelection().getRangeAt(0);
        range.deleteContents();
        var textNode = document.createTextNode(text);
        range.insertNode(textNode);
        range.selectNodeContents(textNode);
        range.collapse(false);

        var selection = window.getSelection();
        selection.removeAllRanges();
        selection.addRange(range);
    }
};
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>
<textarea name="t1"></textarea>
<div style="border: 1px solid;" contenteditable="true">Edit me!</div>
<input />
</body>

请注意,自定义粘贴处理程序仅适用于contenteditable节点。因为textarea和普通的input字段都不支持粘贴HTML内容,所以这里无需采取任何措施。

1
我必须在事件处理程序(第3行)中摆脱.originalEvent才能使其正常工作。因此,完整的代码行看起来像这样:const text = ev.clipboardData ? ev.clipboardData.getData('text/plain') : window.clipboardData.getData('Text');。适用于最新的Chrome,Safari和Firefox。 - Pwdr
需要将 var text = e.originalEvent.clipboardData ? e.originalEvent.clipboardData.getData('text/plain') : window.clipboardData.getData('Text'); 替换为 var text = (e.originalEvent || e).clipboardData.getData('text/plain'); - Mohsen TOA

4

注意,execCommand()已被弃用,在生产环境中请避免使用它。应该使用类似以下的方法:

const editor = document.querySelector('[contentEditable]')
editor.addEventListener('paste', handlePaste)

function handlePaste(e) {
  e.preventDefault()

  const text = (e.clipboardData || window.clipboardData).getData('text')
  const selection = window.getSelection()

  if (selection.rangeCount) {
    selection.deleteFromDocument()
    selection.getRangeAt(0).insertNode(document.createTextNode(text))
  }
}
[contentEditable] {
  padding: 1em;
  border: 1px solid black;
}
<div contentEditable>Paste HTML here</div>

References:


值得注意的是,这并不会让浏览器记录更改的历史,这意味着用户将无法使用CTRL+Z撤消粘贴的文本。 - Beyondo

3

Firefox不允许您访问剪贴板数据,因此您需要进行"黑客"操作才能使其正常工作。我还没有找到完整的解决方案,但是您可以通过创建一个文本区并将内容粘贴到其中来修复ctrl+v粘贴的问题:

//Test if browser has the clipboard object
if (!window.Clipboard)
{
    /*Create a text area element to hold your pasted text
    Textarea is a good choice as it will make anything added to it in to plain text*/           
    var paster = document.createElement("textarea");
    //Hide the textarea
    paster.style.display = "none";              
    document.body.appendChild(paster);
    //Add a new keydown event tou your editor
    editor.addEventListener("keydown", function(e){

        function handlePaste()
        {
            //Get the text from the textarea
            var pastedText = paster.value;
            //Move the cursor back to the editor
            editor.focus();
            //Check that there is a value. FF throws an error for insertHTML with an empty string
            if (pastedText !== "") document.execCommand("insertHTML", false, pastedText);
            //Reset the textarea
            paster.value = "";
        }

        if (e.which === 86 && e.ctrlKey)
        {
            //ctrl+v => paste
            //Set the focus on your textarea
            paster.focus();
            //We need to wait a bit, otherwise FF will still try to paste in the editor => settimeout
            window.setTimeout(handlePaste, 1);
        }

    }, false);
}
else //Pretty much the answer given by pimvdb above
{
    //Add listener for paster to force paste-as-plain-text
    editor.addEventListener("paste", function(e){

        //Get the plain text from the clipboard
        var plain = (!!e.clipboardData)? e.clipboardData.getData("text/plain") : window.clipboardData.getData("Text");
            //Stop default paste action
        e.preventDefault();
        //Paste plain text
        document.execCommand("insertHTML", false, plain);

    }, false);
}

2
在2022年,您可以使用CSS user-modify: read-write-plaintext-only来实现此目的。
<div data-lang="js" data-hide="false" data-console="true" data-babel="false" class="snippet">
<div class="snippet-code">
<pre class="snippet-code-css lang-css prettyprint-override"><code>.plain-text-only {
  user-modify: read-write-plaintext-only;
  -moz-user-modify: read-write-plaintext-only;
  -webkit-user-modify: read-write-plaintext-only;
}

div[contenteditable] {
  padding: 1rem 0.5rem;
  border: 2px solid #eee;
  border-radius: 4px;
  margin-bottom: 2rem;
}
<div contenteditable class="plain-text-only">Can't paste HTML here</div>
<div contenteditable>HTML styled text free</div>

1
这似乎已经过时了。 - webkit

2

我也在处理纯文本粘贴时遇到了execCommand和getData错误,所以我决定采用传统的方式来解决问题,现在它已经完美地运行了:

$('#editor').bind('paste', function(){
    var before = document.getElementById('editor').innerHTML;
    setTimeout(function(){
        var after = document.getElementById('editor').innerHTML;
        var pos1 = -1;
        var pos2 = -1;
        for (var i=0; i<after.length; i++) {
            if (pos1 == -1 && before.substr(i, 1) != after.substr(i, 1)) pos1 = i;
            if (pos2 == -1 && before.substr(before.length-i-1, 1) != after.substr(after.length-i-1, 1)) pos2 = i;
        }
        var pasted = after.substr(pos1, after.length-pos2-pos1);
        var replace = pasted.replace(/<[^>]+>/g, '');
        var replaced = after.substr(0, pos1)+replace+after.substr(pos1+pasted.length);
        document.getElementById('editor').innerHTML = replaced;
    }, 100);
});

您可以在这里找到我做注释的代码: http://www.albertmartin.de/blog/code.php/20/plain-text-paste-with-javascript。该代码与JavaScript相关,可用于纯文本粘贴。请注意保留HTML标签。

1
function PasteString() {
    var editor = document.getElementById("TemplateSubPage");
    editor.focus();
  //  editor.select();
    document.execCommand('Paste');
}

function CopyString() {
    var input = document.getElementById("TemplateSubPage");
    input.focus();
   // input.select();
    document.execCommand('Copy');
    if (document.selection || document.textSelection) {
        document.selection.empty();
    } else if (window.getSelection) {
        window.getSelection().removeAllRanges();
    }
}

以上代码在IE10和IE11中对我有效,并且现在也适用于Chrome和Safari。未在Firefox中测试。


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