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

时间:2015-12-10 00:41:59

标签: javascript clipboard

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

存在以文本(text / plain)形式复制到剪贴板的解决方案,例如https://stackoverflow.com/a/30810322/460084,而不是text / html

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

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

3 个答案:

答案 0 :(得分:24)

Since this answer has gotten some attention, I have completely rewritten the messy original to be easier to grasp. If you want to look at the pre-revisioned version, you can find it here.


The boiled down question:

Can I use JavaScript to copy the formatted output of some HTML code to the users clipboard?


Answer:

Yes, with some limitations, you can.


Solution:

Below is a function that will do exactly that. I tested it with your required browsers, it works in all of them. However, IE 11 will ask for confirmation on that action.

Explanation how this works can be found below, you may interactively test the function out in this 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)
}

Explanation:

Look into the comments in the code above to see where you currently are in the following process:

  1. We create a container to put our HTML code into.
  2. We style the container to be hidden and detect the page's active stylesheets. The reason will be explained shortly.
  3. We put the container into the page's DOM.
  4. We remove possibly existing selections and select the contents of our container.
  5. We do the copying itself. This is actually a multi-step process: Chrome will copy text as it sees it, with applied CSS styles, while other browsers will copy it with the browser's default styles. Therefore we will disable all user styles before copying to get the most consistent result possible.

    1. Before we do this, we prematurely execute the copy command. This is a hack for IE11: In this browser, the copying must be manually confirmed once. Until the user clicked the "Confirm" button, IE users would see the page without any styles. To avoid this, we copy first, wait for confirmation, then disable the styles and copy again. That time we won't get a confirmation dialog since IE remembers our last choice.
    2. We actually disable the page's styles.
    3. Now we execute the copy command again.
    4. We re-enable the stylesheets.
  6. We remove the container from the page's DOM.

And we're done.


Caveats:

  • The formatted content will not be perfectly consistent across browsers.

    As explained above, Chrome (i.e. the Blink engine) will use a different strategy than Firefox and IE: Chrome will copy the contents with their CSS styling, but omitting any styles that are not defined.

    Firefox and IE on the other hand won't apply page-specific CSS, they will apply the browser's default styles. This also means they will have some weird styles applied to them, e.g. the default font (which is usually Times New Roman).

  • For security reasons, browsers will only allow the function to execute as an effect of a user interaction (e.g. a click, keypress etc.)

答案 1 :(得分:4)

我对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复制到剪贴板。

答案 2 :(得分:0)

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

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

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

示例:

copyElementToClipboard(document.body);
copyElementToClipboard('myImageId');