Javascript - 将字符串作为文本/html 复制到剪贴板

IT技术 javascript clipboard
2021-02-25 01:41:14

javascript 中是否有一种方法可以将 html 字符串(即<b>xx<b>)作为文本/html复制到剪贴板中,以便可以将其粘贴到例如具有格式(即 xx 粗体)的 gmail 消息中

存在将文本(文本/纯文本)复制到剪贴板的解决方案,例如https://stackoverflow.com/a/30810322/460084但不是文本/html

我需要一个非 Flash、非 jquery 的解决方案,它至少可以在 IE11 FF42 和 Chrome 上运行。

理想情况下,我想在剪贴板中存储字符串的文本和 html 版本,以便可以根据目标是否支持 html 粘贴正确的版本。

4个回答

由于这个答案受到了一些关注,我将凌乱的原文完全重写,以便更容易掌握。如果您想查看预修订版本,可以在此处找到


归结起来的问题:

我可以使用 JavaScript 将某些 HTML 代码的格式化输出复制到用户剪贴板吗?


回答:

是的,有一些限制,你可以。


解决方案:

下面是一个可以做到这一点的函数。我使用您所需的浏览器对其进行了测试,它适用于所有浏览器。但是,IE 11 将要求对该操作进行确认。

可以在下面找到有关其工作原理的说明,您可以在此jsFiddle 中以交互方式测试该功能

// This function expects an HTML string and copies it as rich text.

function copyFormatted (html) {
  // Create container for the HTML
  // [1]
  var container = document.createElement('div')
  container.innerHTML = html

  // Hide element
  // [2]
  container.style.position = 'fixed'
  container.style.pointerEvents = 'none'
  container.style.opacity = 0

  // Detect all style sheets of the page
  var activeSheets = Array.prototype.slice.call(document.styleSheets)
    .filter(function (sheet) {
      return !sheet.disabled
    })

  // Mount the container to the DOM to make `contentWindow` available
  // [3]
  document.body.appendChild(container)

  // Copy to clipboard
  // [4]
  window.getSelection().removeAllRanges()

  var range = document.createRange()
  range.selectNode(container)
  window.getSelection().addRange(range)

  // [5.1]
  document.execCommand('copy')

  // [5.2]
  for (var i = 0; i < activeSheets.length; i++) activeSheets[i].disabled = true

  // [5.3]
  document.execCommand('copy')

  // [5.4]
  for (var i = 0; i < activeSheets.length; i++) activeSheets[i].disabled = false

  // Remove the container
  // [6]
  document.body.removeChild(container)
}

解释:

查看上面代码中的注释,了解您当前在以下过程中的位置:

  1. 我们创建一个容器来放入我们的 HTML 代码。
  2. 我们将容器设置为隐藏的样式并检测页面的活动样式表。稍后将解释原因。
  3. 我们将容器放入页面的 DOM 中。
  4. 我们删除可能存在的选择并选择我们容器的内容。
  5. 我们自己进行复制。这实际上是一个多步骤的过程:Chrome 将复制它看到的文本并应用 CSS 样式,而其他浏览器将使用浏览器的默认样式复制它。因此,我们将在复制之前禁用所有用户样式,以获得尽可能一致的结果。

    1. 在我们这样做之前,我们过早地执行copy命令。这是 IE11 的一个 hack:在这个浏览器中,复制必须手动确认一次。直到用户单击“确认”按钮,IE 用户才会看到没有任何样式的页面。为了避免这种情况,我们先复制,等待确认,然后禁用样式并再次复制。那时我们不会收到确认对话框,因为 IE 会记住我们的最后选择。
    2. 我们实际上禁用了页面的样式。
    3. 现在我们copy再次执行命令。
    4. 我们重新启用样式表。
  6. 我们从页面的 DOM 中移除容器。

我们已经完成了。


注意事项:

  • 格式化的内容在浏览器之间不会完全一致。

    如上所述,Chrome(即 Blink 引擎)将使用与 Firefox 和 IE 不同的策略:Chrome 将使用其 CSS 样式复制内容,但省略任何未定义的样式。

    另一方面,Firefox 和 IE 不会应用特定于页面的 CSS,它们将应用浏览器的默认样式。这也意味着它们将应用一些奇怪的样式,例如默认字体(通常是Times New Roman)。

  • 出于安全原因,浏览器将只允许该功能作为用户交互(例如点击、按键等)的效果执行。

似乎您违反了安全功能。有点可以理解,你想侵入复制事件,然后实际复制其他东西,这对我来说也是一种风险。您必须 a) 不使用 copy 事件,而是监听 keydown 并且 b) 在复制之前将焦点从 textarea 移开。jsfiddle.net/Loilo/d740eo04/10
2021-04-26 01:41:14
代码还是有问题。我添加了一个 textarea 并在 ctrl+C 上复制,但 FF 失败并且控制台显示“递归过多”!参见jsfiddle.net/d740eo04/7只需在 textarea 中按 ctrl+c
2021-05-05 01:41:14
您始终只能将一件事复制到剪贴板。记事本只是将样式部分去掉。这将以完全相同的方式发生在您复制的 HTML 上(尝试将复制的内容从 JSFiddle 粘贴到记事本)。
2021-05-07 01:41:14
这看起来棒极了 Loilo!如果您可以将其处理为接受 html 字符串(即“xx<b>bold</b>yy”)并将其作为富文本放在剪贴板上的函数,我会将其标记为答案。谢谢 !
2021-05-10 01:41:14
谢谢!将其标记为答案。您认为有没有办法将文本和 html 同时添加到剪贴板,以便根据目标选择正确的版本(即粘贴到记事本中与粘贴到 gmail 中)?
2021-05-10 01:41:14

有一个更简单的解决方案。复制页面(元素)的一部分而不是复制 HTML。

使用这个简单的功能,您可以将页面或整个文档上的任何内容(文本、图像、表格等)复制到剪贴板。该函数接收元素id元素本身。

function copyElementToClipboard(element) {
  window.getSelection().removeAllRanges();
  let range = document.createRange();
  range.selectNode(typeof element === 'string' ? document.getElementById(element) : element);
  window.getSelection().addRange(range);
  document.execCommand('copy');
  window.getSelection().removeAllRanges();
}

如何使用:

copyElementToClipboard(document.body);
copyElementToClipboard('myImageId');
您能否澄清一下,是否可以在循环中复制几个元素(例如两个部分)?谢谢。
2021-04-30 01:41:14
此解决方案运行良好(MacOS 上的 Chrome 和 Firefox)。请注意,事实证明,当我放置按钮调用以在按钮之前选择带有 3 个链接的 div 并且我从 Chrome 复制到 gmail 时,出现了一个方框(按钮?)。添加   就在 <button> 解决这个问题之前。
2021-05-10 01:41:14
@Schroet - 我以这些方式检查过,但没有成功:[A] 每个元素都有一个带有 selectNode 的范围,[B] 为每个范围调用一次 selection.addRange(),[C] 调用 selection.addRange.apply(<selection >,范围)。不知道为什么选择看起来可以添加多个范围......
2021-05-10 01:41:14
这非常有效。在 Chrome 甚至 IE11 中测试!谢谢!
2021-05-18 01:41:14

我对上面 Loilo 的回答做了一些修改:

  • 将焦点设置(并稍后恢复)隐藏的 div 可以防止 FF 在从 textarea 复制时陷入无限递归

  • 将范围设置为 div 的内部子级可防止 chrome<br>在开头插入额外的内容

  • getSelection() 上的 removeAllRanges 防止附加到现有选择(可能不需要)

  • 尝试/抓住 execCommand

  • 更好地隐藏复制 div

在 OSX 上这将不起作用。Safari 不支持 execCommand 并且 chrome OSX 有一个已知错误https://bugs.chromium.org/p/chromium/issues/detail?id=552975

代码:

clipboardDiv = document.createElement('div');
clipboardDiv.style.fontSize = '12pt'; // Prevent zooming on iOS
// Reset box model
clipboardDiv.style.border = '0';
clipboardDiv.style.padding = '0';
clipboardDiv.style.margin = '0';
// Move element out of screen 
clipboardDiv.style.position = 'fixed';
clipboardDiv.style['right'] = '-9999px';
clipboardDiv.style.top = (window.pageYOffset || document.documentElement.scrollTop) + 'px';
// more hiding
clipboardDiv.setAttribute('readonly', '');
clipboardDiv.style.opacity = 0;
clipboardDiv.style.pointerEvents = 'none';
clipboardDiv.style.zIndex = -1;
clipboardDiv.setAttribute('tabindex', '0'); // so it can be focused
clipboardDiv.innerHTML = '';
document.body.appendChild(clipboardDiv);

function copyHtmlToClipboard(html) {
  clipboardDiv.innerHTML=html;

  var focused=document.activeElement;
  clipboardDiv.focus();

  window.getSelection().removeAllRanges();  
  var range = document.createRange(); 
  range.setStartBefore(clipboardDiv.firstChild);
  range.setEndAfter(clipboardDiv.lastChild);
  window.getSelection().addRange(range);  

  var ok=false;
  try {
     if (document.execCommand('copy')) ok=true; else utils.log('execCommand returned false !');
  } catch (err) {
     utils.log('execCommand failed ! exception '+err);
  }

  focused.focus();
}

请参阅jsfiddle,您可以在其中将 html 段输入 textarea 并使用 ctrl+c 复制到剪贴板。

您不想将 tabindex 设置为 0,否则它将位于选项卡索引上,即当用户按下 Tab 键时,它将成为被选中的项目之一。要使其可聚焦,但用户按 Tab 无法访问,请将 tabindex 设置为 -1。我建议将此作为编辑,但被拒绝。
2021-05-11 01:41:14

如果要使用新的Clipboard API,请使用如下write方法

var type = "text/html";
var blob = new Blob([text], { type });
var data = [new ClipboardItem({ [type]: blob })];

navigator.clipboard.write(data).then(
    function () {
    /* success */
    },
    function () {
    /* failure */
    }
);

当前(2021 年 9 月),问题是Firefox 不支持此方法

从版本 87 开始:此功能位于dom.events.asyncClipboard.clipboardItem首选项后面(需要设置为true)。要在 Firefox 中更改首选项,请访问about:config
2021-05-02 01:41:14