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

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

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

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

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


当前回答

当用户通过浏览器的用户界面发起“粘贴”操作时,粘贴事件将被触发。

HTML

<div class="source" contenteditable="true">Try copying text from this box...</div>
<div class="target" contenteditable="true">...and pasting it into this one</div>

JavaScript

const target = document.querySelector('div.target');

target.addEventListener('paste', (event) => {
    let paste = (event.clipboardData || window.clipboardData).getData('text');
    paste = paste.toUpperCase();

    const selection = window.getSelection();
    if (!selection.rangeCount) return false;
    selection.deleteFromDocument();
    selection.getRangeAt(0).insertNode(document.createTextNode(paste));

    event.preventDefault();
});

知道更多

其他回答

这对于Nico的回答来说太长了,我认为它在Firefox上已经不起作用了(根据评论),在Safari上也不起作用了。

首先,您现在似乎可以直接从剪贴板读取。而不是像这样的代码:

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

use:

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有一个类型字段,它是一个DOMStringList,它不实现测试。

Next Firefox将不允许粘贴,除非焦点位于contentteditable =true字段中。

最后,Firefox将不允许可靠地粘贴,除非焦点在文本区域(或者可能是输入),这不仅是contenteditable=true,而且:

不显示:没有 隐藏不可见性: 不是零大小

I was trying to hide the text field so I could make paste work over a JS VNC emulator (i.e. it was going to a remote client and there was no actually textarea etc to paste into). I found trying to hide the text field in the above gave symptoms where it worked sometimes, but typically failed on the second paste (or when the field was cleared to prevent pasting the same data twice) as the field lost focus and would not properly regain it despite focus(). The solution I came up with was to put it at z-order: -1000, make it display:none, make it as 1px by 1px, and set all the colours to transparent. Yuck.

在Safari上,你应用上面的第二部分,即你需要有一个不显示的文本区域:none。

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;

解决方案#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的字符串而导致的错误

简单的解决方案:

document.onpaste = function(e) {
    var pasted = e.clipboardData.getData('Text');
    console.log(pasted)
}

这应该适用于所有支持onpaste事件和突变观察者的浏览器。

这个解决方案不仅仅只是获取文本,它实际上允许您在粘贴到元素之前编辑粘贴的内容。

它通过使用contentteditable, onpaste事件(所有主要浏览器支持)在突变观察者(Chrome, Firefox和IE11+支持)中工作。

步骤1

创建一个带有contentteditable的html元素

<div contenteditable="true" id="target_paste_element"></div>

步骤2

在Javascript代码中添加以下事件

document.getElementById("target_paste_element").addEventListener("paste", pasteEventVerifierEditor.bind(window, pasteCallBack), false);

我们需要绑定pasteCallBack,因为突变观察者将被异步调用。

步骤3

向代码中添加以下函数

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

代码的作用:

Somebody fires the paste event by using ctrl-v, contextmenu or other means In the paste event a new element with contenteditable is created (an element with contenteditable has elevated privileges) The caret position of the target element is saved. The focus is set to the new element The content gets pasted into the new element and is rendered in the DOM. The mutation observer catches this (it registers all changes to the dom tree and content). Then fires the mutation event. The dom of the pasted content gets cloned into a variable and returned to the callback. The temporary element is destroyed. The callback receives the cloned DOM. The caret is restored. You can edit this before you append it to your target. element. In this example I'm using Tim Downs functions for saving/restoring the caret and pasting HTML into the element.

例子

document.getElementById("target_paste_element").addEventListener("paste", pasteEventVerifierEditor.bind(window, pasteCallBack), false); 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; //edit the copied content by slicing pasteHtmlAtCaret(this.innerHTML.slice(3), 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(); } } } div { border: 1px solid black; height: 50px; padding: 5px; } <div contenteditable="true" id="target_paste_element"></div>


非常感谢Tim Down 请看这篇文章的答案:

在粘贴事件上获取文档上的粘贴内容