JavaScript get clipboard data on paste event (Cross browser)

前端 未结 20 2392
小蘑菇
小蘑菇 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:54

    You can do this in this way:

    use this jQuery plugin for pre & post paste events:

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

    Now you can use this plugin;:

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

    Explaination

    First set a uid for all existing elements as data attribute.

    Then compare all nodes POST PASTE event. So by comparing you can identify the newly inserted one because they will have a uid, then just remove style/class/id attribute from newly created elements, so that you can keep your older formatting.

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

    Just let the browser paste as usual in its content editable div and then after the paste swap any span elements used for custom text styles with the text itself. This seems to work okay in internet explorer and the other browsers I tried...

    $('[contenteditable]').on('paste', function (e) {
        setTimeout(function () {
            $(e.target).children('span').each(function () {
                $(this).replaceWith($(this).text());
            });
        }, 0);
    });
    

    This solution assumes that you are running jQuery and that you don't want text formatting in any of your content editable divs.

    The plus side is that it's super simple.

    0 讨论(0)
  • 2020-11-21 12:01

    Based on l2aelba anwser. This was tested on FF, Safari, Chrome, IE (8,9,10 and 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);
                }
            }
        });
    
    0 讨论(0)
  • 2020-11-21 12:02

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

    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+)

    step 1

    Create a HTML-element with contenteditable

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

    step 2

    In your Javascript code add the following event

    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.

    step 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.

    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 Down See this post for the answer:

    Get the pasted content on document on paste event

    0 讨论(0)
  • 2020-11-21 12:05

    This one does not use any setTimeout().

    I have used this great 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

    0 讨论(0)
  • 2020-11-21 12:06

    First that comes to mind is the pastehandler of google's closure lib http://closure-library.googlecode.com/svn/trunk/closure/goog/demos/pastehandler.html

    0 讨论(0)
提交回复
热议问题