JavaScript get clipboard data on paste event (Cross browser)
Web应用程序如何检测粘贴事件并检索要粘贴的数据?
在将文本粘贴到RTF编辑器中之前,我想删除HTML内容。
之后粘贴后清除文本是可行的,但是问题是所有以前的格式都丢失了。 例如,我可以在编辑器中写一个句子并将其设置为粗体,但是当我粘贴新文本时,所有格式都将丢失。 我只想清除粘贴的文本,并保留所有以前的格式。
理想情况下,该解决方案应可在所有现代浏览器(例如MSIE,Gecko,Chrome和Safari)上运行。
请注意,MSIE具有
解决方案1(仅纯文本,需要Firefox 22+)
适用于IE6 +,FF 22 +,Chrome,Safari,Edge
(仅在IE9 +中经过测试,但应在较低版本中使用)
如果需要粘贴<= 22的HTML或Firefox的支持,请参阅解决方案#2。
的HTML
1 | Paste |
的JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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/
请注意,此解决方案为
解决方案#2(HTML且适用于Firefox <= 22)
在IE6 +,FF 3.5 +,Chrome,Safari,Edge中测试
的HTML
1 | Paste |
的JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | 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/
说明
请参阅下面的资源部分。
此功能有两个分支。
第一个检查
如果不支持此方法(所有其他浏览器),那么我们
此功能首先轮询粘贴的数据(每20毫秒一次),这是必需的,因为它不会立即出现。数据出现后:
对粘贴的数据进行任意处理。在这种情况下,我们只是提醒数据,您可以做任何您想做的事。您可能需要通过某种数据清理过程来运行粘贴的数据。
保存和还原光标位置
在实际情况中,您可能希望先保存选择,然后再保存选择(在contentEditable上设置光标位置)。然后,您可以在用户启动粘贴操作时将粘贴的数据插入光标所在的位置。
资源:
- MDN粘贴事件:https://developer.mozilla.org/en-US/docs/Web/Events/paste
- MSDN剪贴板:https://msdn.microsoft.com/zh-cn/library/ms535220(v=vs.85).aspx
- MDN DocumentFragment:https://developer.mozilla.org/en/docs/Web/API/DocumentFragment
- MDN DomStringList:https://developer.mozilla.org/en/docs/Web/API/DOMStringList
感谢Tim Down建议使用DocumentFragment,并感谢由于使用DOMStringList而不是剪贴板数据的字符串而在Firefox中捕获错误。
自从编写此答案以来,情况已经发生了变化:由于Firefox已在版本22中添加了支持,因此所有主要的浏览器现在都支持在粘贴事件中访问剪贴板数据。有关示例,请参见Nico Burns的答案。
过去,这通常不可能以跨浏览器的方式实现。理想的方法是能够通过
当您需要支持较旧的浏览器时,您可以做的事情很多,而且有些hack可以在Firefox 2 +,IE 5.5+和WebKit浏览器(例如Safari或Chrome)中使用。 TinyMCE和CKEditor的最新版本都使用此技术:
请注意,这仅适用于键盘粘贴事件,不适用于上下文或编辑菜单中的粘贴。在粘贴事件触发时,将插入符号重定向到文本区域(至少在某些浏览器中)为时已晚。
万一需要支持Firefox 2,请注意,您需要将textarea放在父文档中,而不是在该浏览器中放置WYSIWYG编辑器iframe的文档。
简单版本:
1 2 3 4 5 | document.querySelector('[contenteditable]').addEventListener('paste', (e) => { e.preventDefault(); const text = (e.originalEvent || e).clipboardData.getData('text/plain'); window.document.execCommand('insertText', false, text); }); |
使用
演示:http://jsbin.com/nozifexasu/edit?js,输出
Edge,Firefox,Chrome,Safari,Opera经过测试。
注意:请记住也要在服务器端检查输入/输出(如PHP剥离标签)
现场演示
已在Chrome / FF / IE11上测试
Chrome / IE令人讨厌的是,这些浏览器为每行添加
选择一些突出显示的HTML并将其粘贴到此处:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | [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; } |
1 |
在这里,我已经用屏幕外的文本区域为Tim Downs提案写了一些概念证明。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | <html> <head> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"> <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"); } }); }); </head> <body class="capture-paste"> <textarea id="area" style="display: none; position: absolute; left: -99em;"></textarea> </body> </html> |
只需将整个代码复制并粘贴到一个html文件中,然后尝试从文档的任何位置的剪贴板粘贴(使用ctrl-v)文本。
我已经在IE9和Firefox,Chrome和Opera的新版本中对其进行了测试。效果很好。也可以使用他喜欢的任何组合键来触发此功能,这也很好。当然,不要忘记包括jQuery源。
随时使用此代码,如果您有一些改进或问题,请发回。还要注意,我不是Java语言开发人员,所以我可能错过了一些东西(=>做自己的见证)。
基于l2aelba anwser。已在FF,Safari,Chrome,IE(8、9、10和11)上进行了测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | $("#editText").on("paste", function (e) { e.preventDefault(); var text; var clp = (e.originalEvent || e).clipboardData; if (clp === undefined || clp === null) { text = window.clipboardData.getData("text") ||""; if (text !=="") { if (window.getSelection) { var newNode = document.createElement("span"); newNode.innerHTML = text; window.getSelection().getRangeAt(0).insertNode(newNode); } else { document.selection.createRange().pasteHTML(text); } } } else { text = clp.getData('text/plain') ||""; if (text !=="") { document.execCommand('insertText', false, text); } } }); |
这个不使用任何setTimeout()。
我已经使用这篇出色的文章来实现跨浏览器支持。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | $(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); } }); |
粘贴之前,此代码使用选择句柄进行了扩展:
演示
为了清除粘贴的文本并将当前选定的文本替换为粘贴的文本,这很简单:
1 | Paste |
JS:
1 2 3 4 | function handlepaste(el, e) { document.execCommand('insertText', false, e.clipboardData.getData('text/plain')); e.preventDefault(); } |
这应该在所有支持onpaste事件和变异观察者的浏览器上都可以使用。
该解决方案超越了仅获取文本的范围,它实际上允许您在粘贴的内容粘贴到元素之前对其进行编辑。
它通过使用contenteditable的onpaste事件(受所有主要浏览器支持)和突变观察者(受Chrome,Firefox和IE11 +支持)来工作
第1步
创建具有contenteditable的HTML元素
1 |
第2步
在您的Javascript代码中添加以下事件
1 | document.getElementById("target_paste_element").addEventListener("paste", pasteEventVerifierEditor.bind(window, pasteCallBack), false); |
我们需要绑定pasteCallBack,因为变异观察者将被异步调用。
第三步
在代码中添加以下功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | function pasteEventVerifierEditor(callback, e) { //is fired on a paste event. //pastes content into another contenteditable div, mutation observer observes this, content get pasted, dom tree is copied and can be referenced through call back. //create temp div //save the caret position. savedCaret = saveSelection(document.getElementById("target_paste_element")); var tempDiv = document.createElement("div"); tempDiv.id ="id_tempDiv_paste_editor"; //tempDiv.style.display ="none"; document.body.appendChild(tempDiv); tempDiv.contentEditable ="true"; tempDiv.focus(); //we have to wait for the change to occur. //attach a mutation observer if (window['MutationObserver']) { //this is new functionality //observer is present in firefox/chrome and IE11 // select the target node // create an observer instance tempDiv.observer = new MutationObserver(pasteMutationObserver.bind(window, callback)); // configuration of the observer: var config = { attributes: false, childList: true, characterData: true, subtree: true }; // pass in the target node, as well as the observer options tempDiv.observer.observe(tempDiv, config); } } function pasteMutationObserver(callback) { document.getElementById("id_tempDiv_paste_editor").observer.disconnect(); delete document.getElementById("id_tempDiv_paste_editor").observer; if (callback) { //return the copied dom tree to the supplied callback. //copy to avoid closures. callback.apply(document.getElementById("id_tempDiv_paste_editor").cloneNode(true)); } document.body.removeChild(document.getElementById("id_tempDiv_paste_editor")); } function pasteCallBack() { //paste the content into the element. restoreSelection(document.getElementById("target_paste_element"), savedCaret); delete savedCaret; pasteHtmlAtCaret(this.innerHTML, false, true); } saveSelection = function(containerEl) { if (containerEl == document.activeElement) { 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) { containerEl.focus(); 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); } function pasteHtmlAtCaret(html, returnInNode, selectPastedContent) { //function written by Tim Down var sel, range; if (window.getSelection) { // IE9 and non-IE sel = window.getSelection(); if (sel.getRangeAt && sel.rangeCount) { range = sel.getRangeAt(0); range.deleteContents(); // Range.createContextualFragment() would be useful here but is // only relatively recently standardized and is not supported in // some browsers (IE9, for one) var el = document.createElement("div"); el.innerHTML = html; var frag = document.createDocumentFragment(), node, lastNode; while ( (node = el.firstChild) ) { lastNode = frag.appendChild(node); } var firstNode = frag.firstChild; range.insertNode(frag); // Preserve the selection if (lastNode) { range = range.cloneRange(); if (returnInNode) { range.setStart(lastNode, 0); //this part is edited, set caret inside pasted node. } else { range.setStartAfter(lastNode); } if (selectPastedContent) { range.setStartBefore(firstNode); } else { range.collapse(true); } sel.removeAllRanges(); sel.addRange(range); } } } else if ( (sel = document.selection) && sel.type !="Control") { // IE < 9 var originalRange = sel.createRange(); originalRange.collapse(true); sel.createRange().pasteHTML(html); if (selectPastedContent) { range = sel.createRange(); range.setEndPoint("StartToStart", originalRange); range.select(); } } |
}
代码的作用是:
非常感谢Tim Down
看到这篇文章的答案:
获取粘贴事件中文档上的粘贴内容
对我有用的解决方案是,如果您要粘贴到文本输入,则将事件侦听器添加到粘贴事件。
由于粘贴事件发生在输入中的文本更改之前,因此在我的粘贴处理程序中,我创建了一个延迟函数,在其中检查粘贴中输入框中发生的更改:
1 2 3 4 5 6 7 | onPaste: function() { var oThis = this; setTimeout(function() { // Defer until onPaste() is done console.log('paste', oThis.input.value); // Manipulate pasted input }, 1); } |
对于Nico的答案发表评论的时间太长了,我认为该评论不再适用于Firefox(根据评论),对Safari而言,它对我也不起作用。
首先,您现在似乎可以直接从剪贴板读取。而不是像这样的代码:
1 2 3 4 | if (/text\/plain/.test(e.clipboardData.types)) { // shouldn't this be writing to elem.value for text/plain anyway? elem.innerHTML = e.clipboardData.getData('text/plain'); } |
采用:
1 2 3 4 5 6 | types = e.clipboardData.types; if (((types instanceof DOMStringList) && types.contains("text/plain")) || (/text\/plain/.test(types))) { // shouldn't this be writing to elem.value for text/plain anyway? elem.innerHTML = e.clipboardData.getData('text/plain'); } |
因为Firefox具有一个
除非焦点位于
最后,除非焦点位于
-
不是
display:none -
不是
visibility:hidden - 大小不为零
我正在尝试隐藏文本字段,以便可以通过JS VNC仿真器进行粘贴工作(即它要发送到远程客户端,并且实际上没有要粘贴的
在Safari上,适用以上第二部分,即您需要具有
首先想到的是Google闭包库的pastehandler
http://closure-library.googlecode.com/svn/trunk/closure/goog/demos/pastehandler.html
简单的解决方案:
1 2 3 4 | document.onpaste = function(e) { var pasted = e.clipboardData.getData('Text'); console.log(pasted) } |
这为我工作:
1 2 3 4 5 6 | function onPasteMe(currentData, maxLen) { // validate max length of pasted text var totalCharacterCount = window.clipboardData.getData('Text').length; } <input type="text" onPaste="return onPasteMe(this, 50);" /> |
1 2 3 4 5 | $('#dom').on('paste',function (e){ setTimeout(function(){ console.log(e.currentTarget.value); },0); }); |
只需让浏览器像往常一样在其内容可编辑div中粘贴,然后在粘贴之后将用于自定义文本样式的所有span元素与文本本身交换即可。在Internet Explorer和我尝试过的其他浏览器中,这似乎可以正常工作。
1 2 3 4 5 6 7 | $('[contenteditable]').on('paste', function (e) { setTimeout(function () { $(e.target).children('span').each(function () { $(this).replaceWith($(this).text()); }); }, 0); }); |
该解决方案假定您正在运行jQuery,并且您不希望任何内容可编辑div中的文本格式设置。
好的一面是它非常简单。
该解决方案是替换html标签,它简单易用且跨浏览器;检查此jsfiddle:http://jsfiddle.net/tomwan/cbp1u2cx/1/,核心代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | var $plainText = $("#plainText"); var $linkOnly = $("#linkOnly"); var $html = $("#html"); $plainText.on('paste', function (e) { window.setTimeout(function () { $plainText.html(removeAllTags(replaceStyleAttr($plainText.html()))); }, 0); }); $linkOnly.on('paste', function (e) { window.setTimeout(function () { $linkOnly.html(removeTagsExcludeA(replaceStyleAttr($linkOnly.html()))); }, 0); }); function replaceStyleAttr (str) { return str.replace(/(<[\w\W]*?)(style)([\w\W]*?>)/g, function (a, b, c, d) { return b + 'style_replace' + d; }); } function removeTagsExcludeA (str) { return str.replace(/<\/?((?!a)(\w+))\s*[\w\W]*?>/g, ''); } function removeAllTags (str) { return str.replace(/<\/?(\w+)\s*[\w\W]*?>/g, ''); } |
注意:您应该对背面的xss过滤器做一些工作,因为此解决方案无法过滤'<< >>'这样的字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | function myFunct( e ){ e.preventDefault(); var pastedText = undefined; if( window.clipboardData && window.clipboardData.getData ){ pastedText = window.clipboardData.getData('Text'); } else if( e.clipboardData && e.clipboardData.getData ){ pastedText = e.clipboardData.getData('text/plain'); } //work with text } document.onpaste = myFunct; |
您可以通过以下方式执行此操作:
使用此jQuery插件进行粘贴前和粘贴后事件:
1 2 3 4 5 6 7 8 9 10 | $.fn.pasteEvents = function( delay ) { if (delay == undefined) delay = 20; return $(this).each(function() { var $el = $(this); $el.on("paste", function() { $el.trigger("prepaste"); setTimeout(function() { $el.trigger("postpaste"); }, delay); }); }); }; |
现在您可以使用此插件;:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | $('#txt').on("prepaste", function() { $(this).find("*").each(function(){ var tmp=new Date.getTime(); $(this).data("uid",tmp); }); }).pasteEvents(); $('#txt').on("postpaste", function() { $(this).find("*").each(function(){ if(!$(this).data("uid")){ $(this).removeClass(); $(this).removeAttr("style id"); } }); }).pasteEvents(); |
讲解
首先,将所有现有元素的uid设置为data属性。
然后比较所有节点的POST PASTE事件。因此,通过比较,您可以识别新插入的元素,因为它们将具有uid,然后只需从新创建的元素中删除style / class / id属性,即可保留较旧的格式。
这是上面发布的现有代码,但我已针对IE对其进行了更新,该错误是当选择现有文本并粘贴不会删除所选内容。以下代码已修复此问题
1 | selRange.deleteContents(); |
请参阅下面的完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | $('[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); } }); |