web应用程序如何检测粘贴事件并检索要粘贴的数据?

我想在文本粘贴到富文本编辑器之前删除HTML内容。

在粘贴后清理文本是有效的,但问题是所有以前的格式都会丢失。例如,我可以在编辑器中编写一个句子并将其加粗,但当我粘贴新文本时,所有格式都会丢失。我只想清除粘贴的文本,并保留以前的任何格式不变。

理想情况下,解决方案应该可以跨所有现代浏览器(例如,MSIE、Gecko、Chrome和Safari)工作。

注意,MSIE有clipboardData.getData(),但我找不到其他浏览器的类似功能。


当前回答

为了在IE11和Chrome上都支持纯文本的复制和粘贴,我使用了以下函数。

它有两个if语句来区分IE和chome,并执行适当的代码。在第一部分中,代码从剪贴板中读取文本,在第二部分中,它将文本粘贴到光标位置,替换当前所选文本。

特别是,对于IE上的粘贴,代码获取选择范围,删除所选文本,将文本从剪贴板插入到一个新的html文本节点中,重新配置范围以在光标位置加上文本长度处插入文本节点。

代码如下:

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

    // Get text from the clipboard.
    var text = '';
    if (e.clipboardData || (e.originalEvent && e.originalEvent.clipboardData)) {
      text = (e.originalEvent || e).clipboardData.getData('text/plain');
    } else if (window.clipboardData) {
      text = window.clipboardData.getData('Text');
    }
    
    // bool to indicate if the user agent is internet explorer
    let isIE = /Trident/.test(navigator.userAgent);
    
    if (document.queryCommandSupported('insertText') && !isIE) {
        // Chrome etc.
        document.execCommand('insertText', false, text);
        
    } else {
        // IE.
        
        // delete content from selection.
        var sel = window.getSelection();
        var range = sel.getRangeAt(0);
        document.execCommand("delete");

        // paste plain text in a new node.
        var newNode = document.createTextNode(text);

        range.insertNode(newNode);
        range.setStart(newNode, 0)
        range.setEnd(newNode, newNode.childNodes.length);

        sel.removeAllRanges;
        sel.addRange(range);
    }

}, false);

特别是为了在IE上粘贴许多答案的文本,我找到了这个说明文档。execCommand('paste', false, text);这在IE11上不起作用,因为浏览器会一次又一次地调用粘贴事件。我用range对象上的函数替换了它。

另一个问题是,在IE11上,根据版本,函数文档。execCommand('insertText', false, text);有时可用,有时不可用,所以我显式检查浏览器是否是IE,并为它执行基于范围选择的部分代码(参见其他)。

其他回答

解决方案#1(纯文本,需要Firefox 22+)

适用于IE6+, FF 22+, Chrome, Safari, Edge (仅在IE9+中测试,但应该适用于较低版本)

如果您需要支持粘贴HTML或Firefox <= 22,请参阅解决方案#2。

函数handlePaste(e) { var clipboardData,粘贴数据; //停止数据实际粘贴到div e.stopPropagation (); e.preventDefault (); //通过剪贴板API获取粘贴数据 clipboardData = e.clipboardData || window.clipboardData; paste data = clipboardData.getData('Text'); //对粘贴数据做任何事情 警报(pastedData); } . getelementbyid(“editableDiv”)。addEventListener(“粘贴”,handlePaste); <div id='editableDiv' contenteditable='true'>粘贴</div>

JSFiddle

注意,这个解决方案为getData函数使用了参数“Text”,这是非标准的。但是,在编写本文时,它可以在所有浏览器中运行。


解决方案#2 (HTML和适用于Firefox <= 22)

测试在IE6+, FF 3.5+, Chrome, Safari, Edge

var editableDiv = document.getElementById('editableDiv'); function handlepaste(e) { var types, pastedData, savedContent; // Browsers that support the 'text/html' type in the Clipboard API (Chrome, Firefox 22+) if (e && e.clipboardData && e.clipboardData.types && e.clipboardData.getData) { // Check for 'text/html' in types list. See abligh's answer below for deatils on // why the DOMStringList bit is needed. We cannot fall back to 'text/plain' as // Safari/Edge don't advertise HTML data even if it is available types = e.clipboardData.types; if (((types instanceof DOMStringList) && types.contains("text/html")) || (types.indexOf && types.indexOf('text/html') !== -1)) { // Extract data and pass it to callback pastedData = e.clipboardData.getData('text/html'); processPaste(editableDiv, pastedData); // Stop the data from actually being pasted e.stopPropagation(); e.preventDefault(); return false; } } // Everything else: Move existing element contents to a DocumentFragment for safekeeping savedContent = document.createDocumentFragment(); while (editableDiv.childNodes.length > 0) { savedContent.appendChild(editableDiv.childNodes[0]); } // Then wait for browser to paste content into it and cleanup waitForPastedData(editableDiv, savedContent); return true; } function waitForPastedData(elem, savedContent) { // If data has been processes by browser, process it if (elem.childNodes && elem.childNodes.length > 0) { // Retrieve pasted content via innerHTML // (Alternatively loop through elem.childNodes or elem.getElementsByTagName here) var pastedData = elem.innerHTML; // Restore saved content elem.innerHTML = ""; elem.appendChild(savedContent); // Call callback processPaste(elem, pastedData); } // Else wait 20ms and try again else { setTimeout(function() { waitForPastedData(elem, savedContent) }, 20); } } function processPaste(elem, pastedData) { // Do whatever with gathered data; alert(pastedData); elem.focus(); } // Modern browsers. Note: 3rd argument is required for Firefox <= 6 if (editableDiv.addEventListener) { editableDiv.addEventListener('paste', handlepaste, false); } // IE <= 8 else { editableDiv.attachEvent('onpaste', handlepaste); } <div id='div' contenteditable='true'>Paste</div>

JSFiddle

解释

div的onpaste事件附加了handlePaste函数,并传递了一个参数:粘贴事件的事件对象。我们特别感兴趣的是这个事件的clipboardData属性,它允许在非ie浏览器中访问剪贴板。在IE中,对应的是window。clipboardData,尽管它有一个稍微不同的API。

请参阅下面的参考资料部分。


句柄粘贴函数:

这个函数有两个分支。

第一个检查事件是否存在。clipboardData和检查它的类型属性是否包含'text/html'(类型可以是使用contains方法检查的DOMStringList,也可以是使用indexOf方法检查的字符串)。如果所有这些条件都满足,那么我们继续执行解决方案#1,只是使用'text/html'而不是'text/plain'。目前Chrome和Firefox 22+都可以使用。

如果此方法不受支持(所有其他浏览器),则我们

将元素的内容保存到一个DocumentFragment中 清空元素 调用waitforpastedata函数


waitforpastedata函数:

这个函数首先轮询粘贴的数据(每20毫秒一次),这是必要的,因为它不会立即出现。当数据出现时:

保存可编辑div的innerHTML(现在是粘贴的数据)到一个变量 恢复保存在DocumentFragment中的内容 使用检索的数据调用'processPaste'函数


processpaste函数:

对粘贴的数据做任意的事情。在这种情况下,我们只是提醒数据,你可以做任何你喜欢的事情。您可能希望通过某种数据消毒过程来运行粘贴的数据。


保存并恢复光标位置

在实际情况下,您可能希望在之前保存选择,然后在之后恢复它(将光标位置设置为contentEditable <div>)。然后,您可以在用户发起粘贴操作时光标所在的位置插入粘贴的数据。

MDN资源

粘贴事件 文档片段 DomStringList

感谢Tim Down建议使用DocumentFragment,以及abligh在Firefox中捕捉由于使用DOMStringList而不是clipboardData.types的字符串而导致的错误

这个没有使用任何setTimeout()。

我使用这篇很棒的文章来实现跨浏览器支持。

$(document).on("focus", "input[type=text],textarea", function (e) {
    var t = e.target;
    if (!$(t).data("EventListenerSet")) {
        //get length of field before paste
        var keyup = function () {
            $(this).data("lastLength", $(this).val().length);
        };
        $(t).data("lastLength", $(t).val().length);
        //catch paste event
        var paste = function () {
            $(this).data("paste", 1);//Opera 11.11+
        };
        //process modified data, if paste occured
        var func = function () {
            if ($(this).data("paste")) {
                alert(this.value.substr($(this).data("lastLength")));
                $(this).data("paste", 0);
                this.value = this.value.substr(0, $(this).data("lastLength"));
                $(t).data("lastLength", $(t).val().length);
            }
        };
        if (window.addEventListener) {
            t.addEventListener('keyup', keyup, false);
            t.addEventListener('paste', paste, false);
            t.addEventListener('input', func, false);
        }
        else {//IE
            t.attachEvent('onkeyup', function () {
                keyup.call(t);
            });
            t.attachEvent('onpaste', function () {
                paste.call(t);
            });
            t.attachEvent('onpropertychange', function () {
                func.call(t);
            });
        }
        $(t).data("EventListenerSet", 1);
    }
}); 

这段代码扩展了粘贴前的选择句柄: 演示

现场演示

在Chrome / FF / IE11上测试

有一个Chrome/IE的烦恼是这些浏览器为每一行添加<div>元素。这里有一篇关于这个的文章,它可以通过设置contentteditable元素为display:inline-block来修复

选择一些突出显示的HTML并粘贴在这里:

function onPaste(e){ var content; e.preventDefault(); if( e.clipboardData ){ content = e.clipboardData.getData('text/plain'); document.execCommand('insertText', false, content); return false; } else if( window.clipboardData ){ content = window.clipboardData.getData('Text'); if (window.getSelection) window.getSelection().getRangeAt(0).insertNode( document.createTextNode(content) ); } } /////// EVENT BINDING ///////// document.querySelector('[contenteditable]').addEventListener('paste', onPaste); [contenteditable]{ /* chroem bug: https://stackoverflow.com/a/24689420/104380 */ display:inline-block; width: calc(100% - 40px); min-height:120px; margin:10px; padding:10px; border:1px dashed green; } /* mark HTML inside the "contenteditable" (Shouldn't be any OFC!)' */ [contenteditable] *{ background-color:red; } <div contenteditable></div>

我写了一个小的概念证明蒂姆唐斯提议这里与屏幕文本区域。下面是代码:

<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script> 
<script language="JavaScript">
 $(document).ready(function()
{

var ctrlDown = false;
var ctrlKey = 17, vKey = 86, cKey = 67;

$(document).keydown(function(e)
{
    if (e.keyCode == ctrlKey) ctrlDown = true;
}).keyup(function(e)
{
    if (e.keyCode == ctrlKey) ctrlDown = false;
});

$(".capture-paste").keydown(function(e)
{
    if (ctrlDown && (e.keyCode == vKey || e.keyCode == cKey)){
        $("#area").css("display","block");
        $("#area").focus();         
    }
});

$(".capture-paste").keyup(function(e)
{
    if (ctrlDown && (e.keyCode == vKey || e.keyCode == cKey)){                      
        $("#area").blur();
        //do your sanitation check or whatever stuff here
        $("#paste-output").text($("#area").val());
        $("#area").val("");
        $("#area").css("display","none");
    }
});

});
</script>

</head>
<body class="capture-paste">

<div id="paste-output"></div>


    <div>
    <textarea id="area" style="display: none; position: absolute; left: -99em;"></textarea>
    </div>

</body>
</html>

只需复制并粘贴整个代码到一个html文件,并尝试粘贴(使用ctrl-v)文本从剪贴板的任何地方的文档。

我在IE9和新版本的Firefox、Chrome和Opera中测试了它。工作得很好。另外,用户可以使用任何他喜欢的组合键来触发这个功能。当然,不要忘记包含jQuery源代码。

请随意使用此代码,如果您有一些改进或问题,请将它们发布回来。还要注意,我不是Javascript开发人员,所以我可能错过了一些东西(=>做你自己的测试)。

这是上面发布的一个现有代码,但我已经为IE更新了它,错误是当现有的文本被选择和粘贴时,不会删除所选的内容。下面的代码已经修复了这个问题

selRange.deleteContents(); 

参见下面的完整代码

$('[contenteditable]').on('paste', function (e) {
    e.preventDefault();

    if (window.clipboardData) {
        content = window.clipboardData.getData('Text');        
        if (window.getSelection) {
            var selObj = window.getSelection();
            var selRange = selObj.getRangeAt(0);
            selRange.deleteContents();                
            selRange.insertNode(document.createTextNode(content));
        }
    } else if (e.originalEvent.clipboardData) {
        content = (e.originalEvent || e).clipboardData.getData('text/plain');
        document.execCommand('insertText', false, content);
    }        
});