【问题标题】:JavaScript get clipboard data on paste event (Cross browser)JavaScript 在粘贴事件中获取剪贴板数据(跨浏览器)
【发布时间】:2011-01-11 17:19:59
【问题描述】:

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

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

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

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

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

【问题讨论】:

  • 所有这些答案都解释了如何获取文本内容。获取图像内容或文件内容需要更多的工作。也许我们可以把标题改成“JavaScript get sanitized text clipboard data...”
  • 就像 nico 说的:event.clipboardData.getData('Text') 为我工作。
  • document.addEventListener('paste'... 为我工作,但如果用户希望能够粘贴到页面上的其他位置,则会导致冲突。然后我尝试了myCanvasElement.addEventListener('paste'...,但没有奏效。最终我发现myCanvasElement.parentElement.addEventListener('paste'... 工作了。

标签: javascript cross-browser clipboard


【解决方案1】:

【讨论】:

  • 这个似乎安全检测到粘贴事件,但似乎无法捕捉/返回粘贴的内容?
  • @Alex:你是对的,这也只适用于 textareas,而不适用于富文本编辑器。
【解决方案2】:

自从写下这个答案后,情况发生了变化:现在 Firefox 在版本 22 中添加了支持,所有主要浏览器现在都支持在粘贴事件中访问剪贴板数据。有关示例,请参阅 Nico Burns's answer

在过去,这通常无法以跨浏览器的方式实现。理想的情况是能够通过paste 事件which is possible in recent browsers 获取粘贴的内容,但不能在某些较旧的浏览器中(尤其是Firefox

当您需要支持较旧的浏览器时,您可以做的是相当复杂的事情,并且需要一些技巧,这将适用于 Firefox 2+、IE 5.5+ 和 WebKit 浏览器,例如 Safari 或 Chrome。 TinyMCE 和 CKEditor 的最新版本都使用了这种技术:

  1. 使用按键事件处理程序检测 ctrl-v / shift-ins 事件
  2. 在该处理程序中,保存当前用户选择,向文档添加屏幕外的 textarea 元素(例如左侧 -1000px),关闭 designMode 并在 textarea 上调用 focus(),从而移动插入符号和有效地重定向粘贴
  3. 在事件处理程序中设置一个非常简短的计时器(例如 1 毫秒)以调用另一个函数来存储 textarea 值,从文档中删除 textarea,重新打开 designMode,恢复用户选择并将文本粘贴到.

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

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

【讨论】:

  • 哇,谢谢!不过,这似乎是一个非常复杂的技巧;-) 您能否再描述一下 designMode 和 selection 的内容,尤其是在第 3 步中?非常感谢!
  • 我有一种可怕的感觉,你会这么问。正如我所说,它涉及很多:我建议查看 TinyMCE 或 CKEditor 的来源,因为我没有时间概述所有涉及的问题。简而言之,designModedocument 的布尔属性,当true 时使整个页面可编辑。 WYSIWYG 编辑器通常使用带有designMode 的 iframe 作为可编辑窗格。保存和恢复用户选择在 IE 中以一种方式完成,在其他浏览器中以另一种方式完成,就像将内容粘贴到编辑器中一样。您需要在IE中获取TextRange,在其他浏览器中获取Range
  • @Samuel:您可以使用paste 事件检测到它,但是到那时将粘贴重定向到另一个元素通常为时已晚,因此此 hack 不起作用。大多数编辑器的回退是显示一个对话框供用户粘贴。
  • 更多信息:Firefox 不允许您在 paste 事件中将焦点移动到另一个元素,但它允许您清除元素的内容(并将其保存到一个变量,以便您以后可以恢复它)。如果此容器是div(它可能也适用于iframe),那么您可以使用普通的dom 方法循环粘贴内容,或者使用innerHTML 将其作为字符串获取。然后您可以恢复div 以前的内容,并插入您喜欢的任何内容。哦,你必须使用与上面相同的计时器技巧。我很惊讶 TinyMCE 不这样做......
  • @ResistDesign:我不同意——这是一种不优雅且复杂的方式来弥补缺乏合理的 API。最好能直接从粘贴事件中获取粘贴的内容,即possible in a limited way in some browsers
【解决方案3】:

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

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

如果您需要支持粘贴 HTML 或 Firefox

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);
<div id='editableDiv' contenteditable='true'>Paste</div>

JSFiddle

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


解决方案 #2(HTML 并且适用于 Firefox

在 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);
}
&lt;div id='div' contenteditable='true'&gt;Paste&lt;/div&gt;

JSFiddle

说明

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

请参阅下面的资源部分。


handlepaste 函数:

这个函数有两个分支。

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

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

  1. 将元素的内容保存到DocumentFragment
  2. 清空元素
  3. 调用waitForPastedData函数

waitforpastedata 函数:

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

  1. 将可编辑 div 的 innerHTML(现在是粘贴的数据)保存到变量中
  2. 恢复保存在 DocumentFragment 中的内容
  3. 使用检索到的数据调用“processPaste”函数

processpaste 函数:

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


保存和恢复光标位置

在实际情况下,您可能希望先保存选择,然后再恢复它 (Set cursor position on contentEditable <div>)。然后,您可以将粘贴的数据插入到用户启动粘贴操作时光标所在的位置。

MDN 上的资源

感谢Tim Down 建议使用 DocumentFragment,并感谢 Firefox 中由于使用 DOMStringList 而不是 clipboardData.types 的字符串而导致的错误

【讨论】:

  • 有趣。我以为我过去曾尝试过,但它在某些浏览器中不起作用,但我相信你是对的。我肯定更喜欢将现有内容移动到DocumentFragment 而不是使用innerHTML,原因如下:首先,您保留任何现有的事件处理程序;第二,保存和恢复innerHTML不保证创建与之前DOM相同的副本;第三,您可以将选择保存为Range,而不必费心添加标记元素或计算文本偏移量(如果您使用innerHTML,则必须这样做)。
  • 确实存在一闪而过的无内容(FONC?),如果处理粘贴的内容需要一些时间,显然会更糟。顺便说一句,为什么在 IE 中提取到 DocumentFragment 会很痛苦?这与其他浏览器中的相同,除非您使用 Range 和 extractContents() 来执行此操作,这在任何情况下都不会比替代方法更简洁。我已经实现了你的技术的一个例子,使用 Rangy 在浏览器中保持美观和统一:jsfiddle.net/bQeWC/4.
  • @Martin:我在 cmets 中发布的 jsFiddle 演示可能会有所帮助。
  • 它似乎不再适用于 Windows 的 Firefox 28(至少)。它永远不会超出waitforpastedata 函数
  • 仅供参考:Edge 现在支持使用 W3C 剪贴板 API 读取 MIME-Type text/html 的数据。在过去,这样的尝试会引发异常。因此,Edge 不再需要这种解决方法/hack。
【解决方案4】:

这对我有用:

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);" />

【讨论】:

    【解决方案5】:

    我在这里用屏幕外的文本区域为 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>
    

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

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

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

    【讨论】:

    • Mac 不使用 ctrl-v 粘贴,而是使用 cmd-v。所以设置 ctrlKey = 91 而不是 17
    • 或者它并不总是 91:stackoverflow.com/questions/3834175/… 无论如何,我很确定 jQuery 会为您处理所有这些,只需检查 e.ctrlKey 或 e.metaKey 我认为。
    • e.ctrlKey 或 e.metaKey 是 JavaScript DOM 的一部分,而不是 jQuery:developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
    • 我认为这不适用于右键单击和粘贴。很多人都采用这种方法。
    【解决方案6】:

    这个没有使用任何 setTimeout()。

    我已经用this的好文章实现了跨浏览器支持。

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

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

    【讨论】:

    • +1 我比 Nico Burns 更喜欢这个,虽然我认为每个人都有自己的位置。
    【解决方案7】:

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

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

    JS:

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

    【讨论】:

    • 你能提供一个演示页面吗?我试过了,还是不行
    【解决方案8】:

    简单版:

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

    使用 clipboardData

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

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

    Document.execCommand() 现在是obsolete


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

    【讨论】:

    • 这很好用,但没有任何版本的 IE 允许从事件中访问剪贴板数据 :( 很好的解决方案,但应该更高!
    • 看起来你可以通过不同的方式在 IE 中获取剪贴板数据,所以如果你检测到 IE,你可以使用该数据而不是提示回退:msdn.microsoft.com/en-us/library/ie/ms535220(v=vs.85).aspx
    • 迄今为止找到的最佳跨浏览器答案。只需添加 IE 及其完美的代码。
    • 这在 IE 中有效(啊,甜蜜,相反的 IE)window.clipboardData.getData('Text');
    • e.preventDefault(); if (e.clipboardData) { content = (e.originalEvent || e).clipboardData.getData('text/plain'); document.execCommand('insertText', false, content); } else if (window.clipboardData) { content = window.clipboardData.getData('Text'); document.selection.createRange().pasteHTML(content); }
    【解决方案9】:

    你可以这样做:

    将此 jQuery 插件用于粘贴前和粘贴后事件:

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

    现在你可以使用这个插件了;:

    $('#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作为数据属性。

    然后比较所有节点的POST PASTE 事件。因此,通过比较您可以识别新插入的元素,因为它们将具有 uid,然后只需从新创建的元素中删除 style/class/id 属性,这样您就可以保留旧格式。

    【讨论】:

      【解决方案10】:

      基于 l2aelba anwser。这是在 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);
                  }
              }
          });
      

      【讨论】:

      • 粘贴到 IE 时有没有办法保留新行?
      【解决方案11】:

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

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

      【讨论】:

      • 不幸的是,恐怖是我们工作描述的一部分;)但我同意,这是一种技巧,只有在用尽所有其他选项时才应使用技巧。
      【解决方案12】:
      $('#dom').on('paste',function (e){
          setTimeout(function(){
              console.log(e.currentTarget.value);
          },0);
      });
      

      【讨论】:

        【解决方案13】:

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

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

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

        第一步

        使用 contenteditable 创建一个 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();
            }
        }
        }
        

        代码的作用:

        1. 有人使用 ctrl-v、上下文菜单或其他方式触发粘贴事件
        2. 在粘贴事件中,创建了一个具有 contenteditable 的新元素(具有 contenteditable 的元素具有提升的权限)
        3. 目标元素的插入符号位置已保存。
        4. 焦点设置在新元素上
        5. 内容被粘贴到新元素中并在 DOM 中呈现。
        6. 突变观察者捕捉到了这一点(它记录了对 dom 树和内容的所有更改)。然后触发突变事件。
        7. 粘贴内容的 dom 被克隆到一个变量中并返回给回调。临时元素被销毁。
        8. 回调接收克隆的 DOM。插入符号已恢复。您可以在将其附加到目标之前对其进行编辑。元素。在此示例中,我使用 Tim Downs 函数来保存/恢复插入符号并将 HTML 粘贴到元素中。

        示例

        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;
        }
        &lt;div contenteditable="true" id="target_paste_element"&gt;&lt;/div&gt;

        非常感谢Tim Down 答案见这篇文章:

        Get the pasted content on document on paste event

        【讨论】:

          【解决方案14】:

          只需让浏览器像往常一样在其内容可编辑的 div 中粘贴,然后在粘贴后将用于自定义文本样式的任何 span 元素与文本本身交换。这在 Internet Explorer 和我尝试过的其他浏览器中似乎可以正常工作...

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

          此解决方案假定您正在运行 jQuery 并且 您不希望在任何内容可编辑的 div 中设置文本格式

          优点是它超级简单。

          【讨论】:

          • 为什么是span标签?我想问题是关于所有标签的。
          【解决方案15】:

          这个解决方案是替换html标签,简单且跨浏览器;检查这个jsfiddle:http://jsfiddle.net/tomwan/cbp1u2cx/1/,核心代码:

          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过滤器的工作,因为这个解决方案不能过滤像'>'这样的字符串

          【讨论】:

          • 服务器上的 XSS 归档与您的 JavaScript 过滤器是否做得好无关。无论如何,黑客都会绕过 100% 的 JS 过滤。
          • 永远不要使用正则表达式来解析/转换 HTML!
          【解决方案16】:

          Live Demo

          在 Chrome / FF / IE11 上测试

          有一个 Chrome/IE 的烦恼是这些浏览器为每个新行添加 &lt;div&gt; 元素。有一篇关于此here 的帖子,可以通过将contenteditable 元素设置为display:inline-block

          来修复它

          选择一些突出显示的 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;
          }
          &lt;div contenteditable&gt;&lt;/div&gt;

          【讨论】:

          • 我需要粘贴为纯文本功能。在 IE9 和 IE10 上测试,效果很好。不用说,它也适用于主流浏览器......谢谢。
          • 您的代码包含一个错误:if(e.originalEvent.clipboardData) 可能导致 NPE,因为您不知道此时是否存在 e.originalEvent
          【解决方案17】:
          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;
          

          【讨论】:

            【解决方案18】:

            对于 Nico 的回答发表评论太长了,我认为它不再适用于 Firefox(根据 cmets),并且不适用于我在 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');
            }
            

            使用:

            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 有一个 types 字段,它是一个 DOMStringList,它没有实现 test

            除非焦点位于 contenteditable=true 字段中,否则下一个 Firefox 将不允许粘贴。

            最后,Firefox 将不允许粘贴可靠,除非焦点位于 textarea(或者可能是输入)中,这不仅是 contenteditable=true,而且:

            • 不是display:none
            • 不是visibility:hidden
            • 大小不为零

            我试图隐藏文本字段,以便可以在 JS VNC 模拟器上进行粘贴工作(即,它会发送到远程客户端,实际上没有 textarea 等可粘贴)。我发现尝试隐藏上面的文本字段有时会出现症状,但通常在第二次粘贴时失败(或者当该字段被清除以防止两次粘贴相同的数据时),因为该字段失去焦点并且无法正确恢复尽管有focus()。我想出的解决方案是把它放在z-order: -1000,使其成为display:none,使其为1px x 1px,并将所有颜色设置为透明。呸。

            在 Safari 上,上述第二部分适用,即您需要有一个不是 display:nonetextarea

            【讨论】:

            • 也许在浏览器渲染引擎上工作的开发人员应该在文档网站上有一个页面或空间,他们可以使用这些页面或空间来写关于他们所从事的功能的注释。例如,如果他们使用粘贴功能,他们会添加,“如果没有显示、隐藏可见性或大小为零,粘贴将不起作用”。
            【解决方案19】:

            这是上面发布的现有代码,但我已经为 IE 更新了它,错误是选择并粘贴现有文本时不会删除所选内容。以下代码已解决此问题

            selRange.deleteContents(); 
            

            查看下面的完整代码

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

            【讨论】:

              【解决方案20】:

              简单的解决方案:

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

              【讨论】:

                【解决方案21】:

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

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

                Know more

                【讨论】:

                  【解决方案22】:

                  为了支持在 IE11 和 Chrome 上复制和粘贴纯文本,我使用了以下功能。

                  它有两个 if 语句来区分 IE 和 chome 并执行适当的代码。在第一部分中,代码从剪贴板中读取文本,在第二部分中,它将文本粘贴到光标位置,替换选中的文本(如果存在)。

                  特别是,对于在 IE 上粘贴,代码获取选择范围,删除所选文本,将剪贴板中的文本插入新的 html 文本节点,重新配置范围以在光标位置插入文本节点加上文本长度。

                  代码如下:

                  editable.addEventListener("paste", function(e) {
                      e.preventDefault();
                  
                      // Get text from the clipboard.
                      var text = '';
                      if (e.clipboardData || (e.originalEvent && e.originalEvent.clipboardData)) {
                        text = (e.originalEvent || e).clipboardData.getData('text/plain');
                      } else if (window.clipboardData) {
                        text = window.clipboardData.getData('Text');
                      }
                      
                      // bool to indicate if the user agent is internet explorer
                      let isIE = /Trident/.test(navigator.userAgent);
                      
                      if (document.queryCommandSupported('insertText') && !isIE) {
                          // Chrome etc.
                          document.execCommand('insertText', false, text);
                          
                      } else {
                          // IE.
                          
                          // delete content from selection.
                          var sel = window.getSelection();
                          var range = sel.getRangeAt(0);
                          document.execCommand("delete");
                  
                          // paste plain text in a new node.
                          var newNode = document.createTextNode(text);
                  
                          range.insertNode(newNode);
                          range.setStart(newNode, 0)
                          range.setEnd(newNode, newNode.childNodes.length);
                  
                          sel.removeAllRanges;
                          sel.addRange(range);
                      }
                  
                  }, false);
                  

                  特别是,为了在 IE 上粘贴许多答案,我发现这条指令 document.execCommand('paste', false, text); 在 IE11 上不起作用,因为浏览器会多次调用粘贴事件。所以我用范围对象上的函数替换了它。

                  另一个问题是,在 IE11 上,根据版本,document.execCommand('insertText', false, text); 函数有时可用,有时不可用,所以我明确检查浏览器是否为 IE,并根据范围选择执行部分代码(见其他)。

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2021-02-07
                    • 2011-07-29
                    • 2015-06-25
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多