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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2176861/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-22 23:06:49  来源:igfitidea点击:

JavaScript get clipboard data on paste event (Cross browser)

javascriptcross-browserclipboard

提问by Alex

How can a web application detect a paste event and retrieve the data to be pasted?

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

I would like to remove HTML content before the text is pasted into a rich text editor.

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

Cleaning the text after being pasted afterwards works, but the problem is that all previous formatting is lost. For example, I can write a sentence in the editor and make it bold, but when I paste new text, all formatting is lost. I want to clean just the text that is pasted, and leave any previous formatting untouched.

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

Ideally, the solution should work across all modern browsers (e.g., MSIE, Gecko, Chrome, and Safari).

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

Note that MSIE has clipboardData.getData(), but I could not find similar functionality for other browsers.

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

采纳答案by Tim Down

The situation has changed since writing this answer: now that Firefox has added support in version 22, all major browsers now support accessing the clipboard data in a paste event. See Nico Burns's answerfor an example.

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

In the past this was not generally possible in a cross-browser way. The ideal would be to be able to get the pasted content via the pasteevent, which is possible in recent browsersbut not in some older browsers (in particular, Firefox < 22).

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

When you need to support older browsers, what you can do is quite involved and a bit of a hack that will work in Firefox 2+, IE 5.5+ and WebKit browsers such as Safari or Chrome. Recent versions of both TinyMCE and CKEditor use this technique:

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

  1. Detect a ctrl-v / shift-ins event using a keypress event handler
  2. In that handler, save the current user selection, add a textarea element off-screen (say at left -1000px) to the document, turn designModeoff and call focus()on the textarea, thus moving the caret and effectively redirecting the paste
  3. Set a very brief timer (say 1 millisecond) in the event handler to call another function that stores the textarea value, removes the textarea from the document, turns designModeback on, restores the user selection and pastes the text in.
  1. 使用按键事件处理程序检测 ctrl-v / shift-ins 事件
  2. 在该处理程序中,保存当前用户选择,向文档添加屏幕外的 textarea 元素(例如左侧 -1000px),designMode关闭并调用focus()textarea,从而移动插入符号并有效地重定向粘贴
  3. 在事件处理程序中设置一个非常短的计时器(比如 1 毫秒)以调用另一个函数来存储 textarea 值,从文档中删除 textarea,designMode重新打开,恢复用户选择并粘贴文本。

Note that this will only work for keyboard paste events and not pastes from the context or edit menus. By the time the paste event fires, it's too late to redirect the caret into the textarea (in some browsers, at least).

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

In the unlikely event that you need to support Firefox 2, note that you'll need to place the textarea in the parent document rather than the WYSIWYG editor iframe's document in that browser.

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

回答by Nico Burns

Solution #1 (Plain Text only and requires Firefox 22+)

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

Works for IE6+, FF 22+, Chrome, Safari, Edge (Only tested in IE9+, but should work for lower versions)

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

If you need support for pasting HTML or Firefox <= 22, see Solution #2.

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

HTML

HTML

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

JavaScript

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/

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

Note that this solution uses the parameter 'Text' for the getDatafunction, which is non-standard. However, it works in all browsers at the time of writing.

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



Solution #2 (HTML and works for Firefox <= 22)

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

Tested in IE6+, FF 3.5+, Chrome, Safari, Edge

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

HTML

HTML

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

JavaScript

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/

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

Explanation

解释

The onpasteevent of the divhas the handlePastefunction attached to it and passed a single argument: the eventobject for the paste event. Of particular interest to us is the clipboardDataproperty of this event which enables clipboard access in non-ie browsers. In IE the equivalent is window.clipboardData, although this has a slightly different API.

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

See resources section below.

请参阅下面的资源部分。



The handlepastefunction:

handlepaste函数:

This function has two branches.

这个函数有两个分支。

The first checks for the existence of event.clipboardDataand checks whether it's typesproperty contains 'text/html' (typesmay be either a DOMStringListwhich is checked using the containsmethod, or a string which is checked using the indexOfmethod). If all of these conditions are fulfilled, then we proceed as in solution #1, except with 'text/html' instead of 'text/plain'. This currently works in Chrome and Firefox 22+.

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

If this method is not supported (all other browsers), then we

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

  1. Save the element's contents to a DocumentFragment
  2. Empty the element
  3. Call the waitForPastedDatafunction
  1. 将元素的内容保存到 DocumentFragment
  2. 清空元素
  3. 调用waitForPastedData函数


The waitforpastedatafunction:

waitforpastedata函数:

This function first polls for the pasted data (once per 20ms), which is necessary because it doesn't appear straight away. When the data has appeared it:

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

  1. Saves the innerHTML of the editable div (which is now the pasted data) to a variable
  2. Restores the content saved in the DocumentFragment
  3. Calls the 'processPaste' function with the retrieved data
  1. 将可编辑 div(现在是粘贴的数据)的 innerHTML 保存到一个变量
  2. 恢复保存在 DocumentFragment 中的内容
  3. 使用检索到的数据调用“processPaste”函数


The processpastefunction:

processpaste函数:

Does arbitrary things with the pasted data. In this case we just alert the data, you can do whatever you like. You will probably want to run the pasted data through some kind of data sanitising process.

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



Saving and restoring the cursor position

保存和恢复光标位置

In a real sitution you would probably want to save the selection before, and restore it afterwards (Set cursor position on contentEditable <div>). You could then insert the pasted data at the position the cursor was in when the user initiated the paste action.

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

Resources:

资源:

Thanks to Tim Down to suggesting the use of a DocumentFragment, and abligh for catching an error in Firefox due to the use of DOMStringList instead of a string for clipboardData.types

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

回答by l2aelba

Simple version:

简单版:

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

UsingclipboardData

使用clipboardData

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

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

Edge, Firefox, Chrome, Safari, Opera tested.

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



Note:Remember to check input/output at server-sidealso (like PHP strip-tags)

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

回答by vsync

Live Demo

现场演示

Tested on Chrome / FF / IE11

在 Chrome / FF / IE11 上测试

There is a Chrome/IE annoyance which is that these browsers add <div>element for each new line. There is a post about this hereand it can be fixed by setting the contenteditableelement to be display:inline-block

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

Select some highlighted HTML and paste it here:

选择一些突出显示的 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>

回答by JanM

I've written a little proof of concept for Tim Downs proposal here with off-screen textarea. And here goes the code:

我在这里为 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>

Just copy and paste the whole code into one html file and try to paste (using ctrl-v) text from clipboard anywhere on the document.

只需将整个代码复制并粘贴到一个 html 文件中,然后尝试将剪贴板中的文本粘贴(使用 ctrl-v)到文档的任何位置。

I've tested it in IE9 and new versions of Firefox, Chrome and Opera. Works quite well. Also it's good that one can use whatever key combination he prefers to triger this functionality. Of course don't forget to include jQuery sources.

我已经在 IE9 和新版本的 Firefox、Chrome 和 Opera 中对其进行了测试。效果很好。此外,可以使用他喜欢的任何组合键来触发此功能也很好。当然不要忘记包含 jQuery 源。

Feel free to use this code and if you come with some improvements or problems please post them back. Also note that I'm no Javascript developer so I may have missed something (=>do your own testign).

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

回答by tmorell

Based on l2aelbaanwser. This was tested on FF, Safari, Chrome, IE (8,9,10 and 11)

基于l2aelbaanwser。这已在 FF、Safari、Chrome、IE(8、9、10 和 11)上进行了测试

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

回答by tmorell

This one does not use any setTimeout().

这个不使用任何 setTimeout()。

I have used thisgreat article to achieve cross browser support.

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

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

This code is extended with selection handle before paste: demo

此代码在粘贴之前使用选择句柄进行了扩展: 演示

回答by Matt Crinklaw-Vogt

For cleaning the pasted textand replacing the currently selected text with the pasted textthe matter is pretty trivial:

为了清理粘贴的文本粘贴的文本替换当前选定的文本,这件事非常简单:

<div id='div' contenteditable='true' onpaste='handlepaste(this, event)'>Paste</div>

JS:

JS:

function handlepaste(el, e) {
  document.execCommand('insertText', false, e.clipboardData.getData('text/plain'));
  e.preventDefault();
}

回答by Mouser

This should work on all browsers that support the onpaste event and the mutation observer.

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

This solution goes a step beyond getting the text only, it actually allows you to edit the pasted content before it get pasted into an element.

此解决方案比仅获取文本更进一步,它实际上允许您在粘贴到元素之前编辑粘贴的内容。

It works by using contenteditable, onpaste event (supported by all major browsers) en mutation observers (supported by Chrome, Firefox and IE11+)

它通过使用 contenteditable、onpaste 事件(所有主要浏览器都支持)突变观察者(Chrome、Firefox 和 IE11+ 支持)来工作

step 1

第1步

Create a HTML-element with contenteditable

创建一个具有 contenteditable 的 HTML 元素

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

step 2

第2步

In your Javascript code add the following event

在您的 Javascript 代码中添加以下事件

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

We need to bind pasteCallBack, since the mutation observer will be called asynchronously.

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

step 3

第 3 步

Add the following function to your code

将以下函数添加到您的代码中

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

What the code does:

代码的作用:

  1. Somebody fires the paste event by using ctrl-v, contextmenu or other means
  2. In the paste event a new element with contenteditable is created (an element with contenteditable has elevated privileges)
  3. The caret position of the target element is saved.
  4. The focus is set to the new element
  5. The content gets pasted into the new element and is rendered in the DOM.
  6. The mutation observer catches this (it registers all changes to the dom tree and content). Then fires the mutation event.
  7. The dom of the pasted content gets cloned into a variable and returned to the callback. The temporary element is destroyed.
  8. 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.
  1. 有人使用 ctrl-v、contextmenu 或其他方式触发 paste 事件
  2. 在粘贴事件中,创建了一个具有 contenteditable 的新元素(具有 contenteditable 的元素具有提升的权限)
  3. 保存目标元素的插入符号位置。
  4. 焦点设置在新元素上
  5. 内容被粘贴到新元素中并在 DOM 中呈现。
  6. 突变观察者捕捉到这一点(它注册对 dom 树和内容的所有更改)。然后触发突变事件。
  7. 粘贴内容的 dom 被克隆到一个变量中并返回给回调。临时元素被销毁。
  8. 回调接收克隆的 DOM。插入符号被恢复。您可以在将其附加到目标之前对其进行编辑。元素。在这个例子中,我使用 Tim Downs 函数来保存/恢复插入符号并将 HTML 粘贴到元素中。

Example

例子

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>



Many thanks to Tim DownSee this post for the answer:

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

Get the pasted content on document on paste event

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

回答by Lex

Solution that works for me is adding event listener to paste event if you are pasting to a text input. Since paste event happens before text in input changes, inside my on paste handler I create a deferred function inside which I check for changes in my input box that happened on paste:

如果您要粘贴到文本输入,那么对我有用的解决方案是添加事件侦听器以粘贴事件。由于粘贴事件发生在输入更改中的文本之前,因此在我的粘贴处理程序中,我创建了一个延迟函数,在其中检查输入框中粘贴时发生的更改:

onPaste: function() {
    var oThis = this;
    setTimeout(function() { // Defer until onPaste() is done
        console.log('paste', oThis.input.value);
        // Manipulate pasted input
    }, 1);
}