JavaScript get clipboard data on paste event (Cross browser)

前端 未结 20 2393
小蘑菇
小蘑菇 2020-11-21 11:22

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

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

相关标签:
20条回答
  • 2020-11-21 11:46

    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 answer for an example.

    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 paste event, which is possible in recent browsers but not in some older browsers (in particular, 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:

    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 designMode off 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 designMode back on, restores the user selection and pastes the text in.

    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.

    0 讨论(0)
  • 2020-11-21 11:46

    This is an existing code posted above but I have updated it for IE's, the bug was when the existing text is selected and pasted will not delete the selected content. This has been fixed by the below code

    selRange.deleteContents(); 
    

    See complete code below

    $('[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);
        }        
    });
    
    0 讨论(0)
  • 2020-11-21 11:49

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

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

    If you need support for pasting HTML or Firefox <= 22, see Solution #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/

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


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

    Tested in 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/

    Explanation

    The onpaste event of the div has the handlePaste function attached to it and passed a single argument: the event object for the paste event. Of particular interest to us is the clipboardData property 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.

    See resources section below.


    The handlepaste function:

    This function has two branches.

    The first checks for the existence of event.clipboardData and checks whether it's types property contains 'text/html' (types may be either a DOMStringList which is checked using the contains method, or a string which is checked using the indexOf method). 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+.

    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 waitForPastedData function

    The waitforpastedata function:

    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:

    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

    The processpaste function:

    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.

    Resources:

    • MDN paste event: https://developer.mozilla.org/en-US/docs/Web/Events/paste
    • MSDN clipboard: https://msdn.microsoft.com/en-us/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

    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

    0 讨论(0)
  • 2020-11-21 11:50

    Live Demo

    Tested on 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 here and it can be fixed by setting the contenteditable element to be display:inline-block

    Select some highlighted HTML and paste it here:

    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>

    0 讨论(0)
  • 2020-11-21 11:51

    Simple solution:

    document.onpaste = function(e) {
        var pasted = e.clipboardData.getData('Text');
        console.log(pasted)
    }
    
    0 讨论(0)
  • 2020-11-21 11:52
    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;
    
    0 讨论(0)
提交回复
热议问题