剪贴板API和操作系统之间的浏览器行为不一致

时间:2019-05-14 14:58:50

标签: javascript reactjs clipboard

我正在尝试解决React应用程序的Windows用户无法复制和粘贴非Windows用户可以粘贴的内容的问题。

我们在反应模式窗口中显示内容,并带有一个按钮,当单击该按钮时,它会突出显示要复制的区域,然后复制到剪贴板缓冲区以进行粘贴。

我们正在通过按钮上的click事件触发的onCopy事件中运行以下内容:

const range = document.createRange();
range.selectNode(this.getCiteTextNode()); // function to get the node
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);

const listener = document.addEventListener("copy", e => {
  const outerHTML = this.getCitationTextWithoutStyle(); //function strips styling attributes
  e.clipboardData.setData("text/html", outerHTML);
  e.preventDefault();
});

document.execCommand("copy");
document.removeEventListener("copy", listener);

我观察到的是,尽管MacOS上的浏览器(Safari,Firefox,Chrome)允许进行此复制/粘贴操作,但在Windows,Edge,FF和Chrome中,它们会静默失败(剪贴板缓冲区中没有任何内容),即使已在这些浏览器中授予了程序访问权限。更重要的是,任何复制粘贴请求(菜单,鼠标或键盘)都无提示地失败;我可以手动突出显示模式的其他区域,选择复制,然后尝试粘贴到另一个文档中,但是它根本不会复制-没有东西可以将其复制到缓冲区中。

但是,确实在IE11中起作用-带有提示-但是在触发按钮单击事件时剪贴板访问行为正常。

进一步的实验表明,当我将preventDefault更改为stopPropagation时,这在Windows浏览器上有效。完成此操作后,内容将正确地放入剪贴板。

有人遇到过吗?如何防止事件冒泡如何在仅Windows浏览器中使此“工作”?

PS-了解execCommand的草稿状态。

谢谢

1 个答案:

答案 0 :(得分:0)

序言 此答案试图解释OP中的代码在做什么,他们可能会遇到什么以及如何处理。他们应该处理它,但是,Windows和macO对我而言实际上表现相同,这一事实使它有所削弱...


如果我们逐步执行您的代码,那就是

  • 在DOM中选择一个节点。
  • 执行copy命令
    [内部浏览器逻辑]
    =>触发copy事件
  • 处理copy事件
  • 设置事件的DataTransfer数据。
    =>在剪贴板中将outerHTML设置为text/html
  • 防止copy事件的默认操作。

如果没有阻止,则应该是“ 默认操作

==>在剪贴板中将活动选择的标记抓取为text/HTML
  =>在剪贴板中将所选内容的toString()抓取为text/plain

也许您已经看到缺少的代码:

您未设置复制事件的text/plain

只有少数应用程序会尝试从剪贴板中获取text/html数据,大多数应用程序只会搜索text/plain值。

所以您也需要设置它。

这里是一个小操场*,您可以在其中看到即使在网页中,也只有某些地方允许粘贴为text/html,而其他地方将使用text/plain值。

btn.onclick = e => {
  const range = document.createRange();
  range.selectNode(copyme); // function to get the node
  const selection = getSelection();
  selection.removeAllRanges();
  selection.addRange(range);

  const listener = e => {
    // make it green in the clipboard
    const outerHTML = copyme.outerHTML.replace('red', 'green');
    // add some text to show we have the control
    const plaintext = selection.toString() + ' plaintext';

    const dT = e.clipboardData;
    dT.setData("text/html", outerHTML);
    // IIRC IE does only support `"text"` MIME type
    dT.setData("text", plaintext);
    e.preventDefault();
  };

  document.addEventListener("copy", listener);
  document.execCommand("copy");
  document.removeEventListener("copy", listener);
};
[contenteditable] {border: 1px solid;}
<button id="btn">copy</button>

<span id="copyme" style="color:red">Hello</span>
<br>

<!-- contenteditable elements will grab the text/html data -->
<div contenteditable>paste HTML here<br> </div>

<!-- <textarea> and <input> will only grab the text/plain -->
<textarea>paste plain here
</textarea>

*我从您的原始代码中修复了一些不相关的错别字,例如您的问题注释中指出的错误事件删除。


还要注意,并不是stopPropagation在做任何魔术,而仅仅是您没有致电preventDefault的事实。
这样做,将this.getCiteTextNode()返回的Node的标记复制为text/html并将其textContent复制为text/plain的默认行为是覆盖您自己的设置。

这是一个带有复选框的示例,该复选框控制是否应阻止默认行为。您会看到,如果不阻止它,则复制的富文本仍然是 red ,而不是我们在事件处理程序中设置的绿色,而纯文本仍然是节点的textContent,编辑的文本中。

btn.onclick = e => {
  const range = document.createRange();
  range.selectNode(copyme); // function to get the node
  const selection = getSelection();
  selection.removeAllRanges();
  selection.addRange(range);

  const listener = e => {
    // make it green in the clipboard
    const outerHTML = copyme.outerHTML.replace('red', 'green');
    // add some text to show we have the control
    const plaintext = selection.toString() + ' plaintext';

    const dT = e.clipboardData;
    dT.setData("text/html", outerHTML);
    // IIRC IE does only support `"text"` MIME type
    dT.setData("text", plaintext);
	  if(prev.checked) {
      e.preventDefault();
    }
  };

  document.addEventListener("copy", listener);
  document.execCommand("copy");
  document.removeEventListener("copy", listener);
};
[contenteditable] {border: 1px solid;}
<label>prevent default behavior<input type="checkbox" id="prev" checked></label><br>
<button id="btn">copy</button>

<span id="copyme" style="color:red">Hello</span>
<br>

<!-- contenteditable elements will grab the text/html data -->
<div contenteditable>paste HTML here<br> </div>

<!-- <textarea> and <input> will only grab the text/plain -->
<textarea>paste plain here
</textarea>