JavaScript 在粘贴事件上获取剪贴板数据(跨浏览器)

IT技术 javascript cross-browser clipboard
2021-01-25 03:47:29

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

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

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

理想情况下,该解决方案应该适用于所有现代浏览器(例如 MSIE、Gecko、Chrome 和 Safari)。

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

6个回答

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

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

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

HTML

<div id='editableDiv' contenteditable='true'>Paste</div>

JavaScript

function handlePaste (e) {
    var clipboardData, pastedData;

    // Stop data actually being pasted into div
    e.stopPropagation();
    e.preventDefault();

    // Get pasted data via clipboard API
    clipboardData = e.clipboardData || window.clipboardData;
    pastedData = clipboardData.getData('Text');
    
    // Do whatever with pasteddata
    alert(pastedData);
}

document.getElementById('editableDiv').addEventListener('paste', handlePaste);

JSFiddle:https ://jsfiddle.net/swL8ftLs/12/

请注意,此解决方案对getData函数使用参数“文本” ,这是非标准的。但是,在撰写本文时,它适用于所有浏览器。


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

在 IE6+、FF 3.5+、Chrome、Safari、Edge 中测试

HTML

<div id='div' contenteditable='true'>Paste</div>

JavaScript

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);
}

JSFiddle:https ://jsfiddle.net/nicoburns/wrqmuabo/23/

解释

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

请参阅下面的资源部分。


handlepaste函数:

这个函数有两个分支。

第一个检查event.clipboardData是否存在并检查它的types属性是否包含 'text/html'(types可以DOMStringList是使用contains方法检查的a 或使用indexOf方法检查的字符串)。如果所有这些条件都满足,那么我们按照解决方案 #1 进行处理,除了使用 'text/html' 而不是 'text/plain'。这目前适用于 Chrome 和 Firefox 22+。

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

  1. 将元素的内容保存到 DocumentFragment
  2. 清空元素
  3. 调用waitForPastedData函数

waitforpastedata函数:

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

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

processpaste函数:

对粘贴的数据进行任意操作。在这种情况下,我们只是提醒数据,您可以随心所欲。您可能希望通过某种数据清理过程来运行粘贴的数据。


保存和恢复光标位置

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

资源:

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

确实有闪现的无内容(FONC?),如果粘贴内容的处理需要一些时间,显然会更糟。顺便说一句,为什么DocumentFragment在 IE 中提取会很痛苦?它与其他浏览器中的相同,除非您使用 Range 并extractContents()执行它,这在任何情况下都不比替代方案更简洁。我已经实现了你的技术的一个例子,使用 Rangy 在浏览器中保持良好和统一:jsfiddle.net/bQeWC/4
2021-03-21 03:47:29
它似乎不再适用于 Windows 的 Firefox 28(至少)。它永远不会超出waitforpastedata功能
2021-03-21 03:47:29
仅供参考:Edge 现在支持text/html使用 W3C 剪贴板 API以 MIME 类型读取数据在过去,这样的尝试会引发异常。因此,Edge 不再需要这种变通方法/hack。
2021-03-24 03:47:29
有趣的。我以为我过去试过这个,但它在某些浏览器中不起作用,但我确定你是对的。出于以下几个原因,我绝对更喜欢将现有内容移动到 aDocumentFragment而不是使用innerHTML:首先,您保留任何现有的事件处理程序;其次,保存和恢复innerHTML不能保证创建与之前 DOM 相同的副本;第三,您可以将选择保存为 aRange而不必费心添加标记元素或计算文本偏移(如果您使用 ,这是您必须做的innerHTML)。
2021-04-02 03:47:29
@Martin:我在评论中发布的 jsFiddle 演示可能会有所帮助。
2021-04-07 03:47:29

自从写下这个答案以来,情况发生了变化:既然 Firefox 在版本 22 中增加了支持,所有主要浏览器现在都支持在粘贴事件中访问剪贴板数据。Nico Burns 的回答为例。

在过去,这在跨浏览器的方式中通常是不可能的。理想的情况是能够通过paste事件获取粘贴的内容这在最近的浏览器中是可能的,但在一些较旧的浏览器(特别是 Firefox < 22)中是不可能的

当您需要支持较旧的浏览器时,您可以做的非常复杂,并且可以在 Firefox 2+、IE 5.5+ 和 WebKit 浏览器(如 Safari 或 Chrome)中使用。TinyMCE 和 CKEditor 的最新版本都使用这种技术:

  1. 使用按键事件处理程序检测 ctrl-v / shift-ins 事件
  2. 在该处理程序中,保存当前用户选择,向文档添加屏幕外的 textarea 元素(例如左侧 -1000px),designMode关闭并调用focus()textarea,从而移动插入符号并有效地重定向粘贴
  3. 在事件处理程序中设置一个非常短的计时器(比如 1 毫秒)来调用另一个函数来存储 textarea 值,从文档中删除 textarea,designMode重新打开,恢复用户选择并粘贴文本。

请注意,这仅适用于键盘粘贴事件,而不适用于上下文或编辑菜单中的粘贴。当粘贴事件触发时,将插入符号重定向到文本区域(至少在某些浏览器中)为时已晚。

万一您需要支持 Firefox 2,请注意您需要将 textarea 放置在父文档中,而不是该浏览器中的 WYSIWYG 编辑器 iframe 文档中。

关于此的更多信息:Firefox 不允许您将焦点移动到paste事件中的另一个元素,但是它允许您清除元素的内容(并将其保存到变量中,以便您以后可以恢复它)。如果这个容器是一个div(它可能也适用于一个iframe),那么你可以使用普通的 dom 方法循环粘贴内容,或者使用innerHTML. 然后,您可以恢复 之前的内容div,并插入您喜欢的任何内容。哦,您必须使用与上述相同的计时器技巧。我很惊讶 TinyMCE 不这样做......
2021-03-11 03:47:29
我有一种可怕的感觉,你会问这个。正如我所说,它非常复杂:我建议查看 TinyMCE 或 CKEditor 的来源,因为我没有时间概述所涉及的所有问题。简而言之,designMode是 的布尔属性,document并且在 时使整个页面可编辑trueWYSIWYG 编辑器通常使用带有designModeon的 iframe作为可编辑窗格。保存和恢复用户选择在 IE 中以一种方式完成,在其他浏览器中以另一种方式完成,就像将内容粘贴到编辑器中一样。您需要TextRange在 IE 中获取 a Range在其他浏览器中获取 a
2021-03-18 03:47:29
@Samuel:您可以使用该paste事件检测它,但到那时将粘贴重定向到另一个元素通常为时已晚,因此此 hack 将不起作用。大多数编辑器的回退是显示一个对话框供用户粘贴。
2021-03-21 03:47:29
哇,谢谢!不过,这似乎是一个非常复杂的 hack ;-) 您能否多描述一下 designMode 和 selection 的事情,尤其是在第 3 步中?非常感谢!
2021-03-22 03:47:29
@ResistDesign:我不同意 - 这是一种弥补缺乏合理 API 的不优雅和复杂的方式。能够直接从 paste 事件中获取粘贴的内容会更好,这在某些浏览器中以有限的方式可能的
2021-03-28 03:47:29

简单版:

document.querySelector('[contenteditable]').addEventListener('paste', (e) => {
    e.preventDefault();
    const text = (e.originalEvent || e).clipboardData.getData('text/plain');
    window.document.execCommand('insertText', false, text);
});

使用 clipboardData

演示: http : //jsbin.com/nozifexasu/edit?js,output

Edge、Firefox、Chrome、Safari、Opera 已测试。

Document.execCommand() 现在已经过时了。


注意:记住还要检查服务器端的输入/输出(如PHP strip-tags

迄今为止找到的最佳跨浏览器答案。只需添加IE及其完美的代码。
2021-03-10 03:47:29
看起来您可以通过不同的方式在 IE 中获取剪贴板数据,因此如果您检测到 IE,您可以使用该数据而不是提示回退:msdn.microsoft.com/en-us/library/ie/ms535220(v =vs.85).aspx
2021-03-13 03:47:29
这真的很好用,但是没有任何版本的 IE 允许从事件访问剪贴板数据 :( 很好的解决方案,不过,这应该更高!
2021-03-17 03:47:29
这适用于 IE(啊,甜,相反的 IE) window.clipboardData.getData('Text');
2021-03-26 03:47:29
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); }
2021-04-09 03:47:29

现场演示

在 Chrome / FF / IE11 上测试

有一个 Chrome/IE 烦恼,即这些浏览器<div>为每个新行添加元素。有一个关于这个职位在这里,它可以通过设置固定CONTENTEDITABLE元素是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>

您的代码包含一个错误: if(e.originalEvent.clipboardData) 可能会导致 NPE,因为您不知道此时是否存在 e.originalEvent
2021-03-11 03:47:29
我需要粘贴为纯文本功能。在 IE9 和 IE10 上测试过,效果很好。不用说,它也适用于主要浏览器......谢谢。
2021-04-08 03:47:29

我在这里为 Tim Downs 的提案写了一些关于屏幕外文本区域的概念证明。代码如下:

<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 开发人员,所以我可能错过了一些东西(=> 做你自己的测试)。

Mac 不使用 ctrl-v 粘贴,它们使用 cmd-v。所以设置 ctrlKey = 91 而不是 17
2021-03-29 03:47:29
或者它并不总是 91:stackoverflow.com/questions/3834175/... 无论如何,我很确定 jQuery 会为您处理所有这些,我认为只需检查 e.ctrlKey 或 e.metaKey。
2021-03-29 03:47:29
我认为这不适用于右键单击和粘贴。很多人采取这种方法。
2021-03-29 03:47:29
e.ctrlKey 或 e.metaKey 是 JavaScript DOM 的一部分,而不是 jQuery:developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
2021-04-08 03:47:29