首页 > 解决方案 > 剪贴板 API 和操作系统之间不一致的浏览器行为

问题描述

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

我们在 react-modal 窗口中显示内容,带有一个按钮,单击该按钮时,将突出显示要复制的区域,并复制到剪贴板缓冲区以进行粘贴。

我们在按钮上的 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 中确实有效 - 有提示 - 但当按钮单击事件被触发时,剪贴板访问行为正常。

进一步的实验表明,当我更改preventDefaultstopPropagation. 一旦我这样做了,内容就会正确地进入剪贴板。

有没有人遇到过这个?防止事件冒泡如何在仅限 Windows 的浏览器中“工作”?

PS - 了解 execCommand 的草稿状态。

谢谢

标签: javascriptreactjsclipboard

解决方案


序言这个答案试图解释 OP 中的代码在做什么,他们可能面临什么,以及他们应该如何处理它,但是,Windows 和 macOS 对我来说实际上表现相同这一事实有点削弱了它。 ..


如果我们一步一步地采取你的代码,它是

  • 在 DOM 中选择一个节点。
  • 执行copy命令
    [内部浏览器逻辑]
      => 触发copy事件
  • 处理copy事件
  • 设置事件的 DataTransfer 数据。
    => 设置outerHTMLtext/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.
这样做,复制 as 返回的节点的标记及其 textContent as 的默认行为将this.getCiteTextNode()覆盖text/htmltext/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>


推荐阅读