javascript copy element to clipboard with all styles
Asked Answered
M

3

11

I am trying to copy a div to the clipboard. The div has a few styles, including a background. I have made a script to copy the div to the clipboard, but I can't figure out how to do the background.

I have seen this done, but I can't remember how.

Any help would be appreciated.


This is as far as I got:

function executeCopy(text) {
    var copyDiv = document.createElement('div');
    copyDiv.contentEditable = true;
    document.body.appendChild(copyDiv);
    copyDiv.innerHTML = text;
    copyDiv.unselectable = "off";
    copyDiv.focus();
    document.execCommand('SelectAll');
    document.execCommand("Copy", false, null);
    document.body.removeChild(copyDiv);
}
#foo {
  background-color: red;
  font-family: cursive;
}
<div id="foo">Test</div>
<button onclick="executeCopy(document.getElementById('foo').innerHTML);">Copy</button>
Megdal answered 1/2, 2018 at 1:31 Comment(0)
G
15

You can try the following fiddle. It works with every text style there is and it's interoperable with MS Word and Pages (I just tested it).

The code is pretty straightforward so I wouldn't really get into the depths of it, but feel free to drop me any questions should you feel like. :)

const copyWithStyle = ( element ) => {

    const doc = document;
    const text = doc.getElementById( element );
    let range;
    let selection;

    if( doc.body.createTextRange ) {

        range = doc.body.createTextRange();
        range.moveToElement( text );
        range.select();

    } else if ( window.getSelection ) {

        selection = window.getSelection();

        range = doc.createRange();
        range.selectNodeContents( text );

        selection.removeAllRanges();
        selection.addRange( range );

    }

    document.execCommand( 'copy' );
    window.getSelection().removeAllRanges();
    document.getElementById( 'clickMe' ).value = 'Copied to clipboard!';

}

https://jsfiddle.net/aypdg3kL/

From the example in JSFiddle

HTML

<div id="text" style="color:red">
    <i>Hello</i> world!
</div>
<input id="btn" onclick="CopyToClipboard('text')" type="button" value="Copy" />

JS

function CopyToClipboard(element) {

        var doc = document
        , text = doc.getElementById(element)
        , range, selection;
    
    if (doc.body.createTextRange)
    {
        range = doc.body.createTextRange();
        range.moveToElementText(text);
        range.select();
    } 
    
    else if (window.getSelection)
    {
        selection = window.getSelection();        
        range = doc.createRange();
        range.selectNodeContents(text);
        selection.removeAllRanges();
        selection.addRange(range);
    }
    document.execCommand('copy');
    window.getSelection().removeAllRanges();
    document.getElementById("btn").value="Copied";
}
Godden answered 1/2, 2018 at 1:44 Comment(0)
L
1

The accepted answer implementation has an issue with cross-browser compatibility in 2024.

Uncaught (in promise) TypeError: Failed to execute 'selectNodeContents' on 'Range': parameter 1 is not of type 'Node'.
    at copyElementToClipboard

Which is caused by line: range.selectNodeContents(text);

Using moveToElementText is the safer and more correct choice (fixes the issue):

      function copyElementToClipboard(element) {
        let range;
        let selection;

        if (!element) {
          console.error("No element to copy");
          return;
        }

        if (document.body.createTextRange) {
          range = document.body.createTextRange();
          // correct method for text range
          range.moveToElementText(element);
          range.select();
        } else if (window.getSelection) {
          selection = window.getSelection();

          range = document.createRange();
          // ensuring textElement is passed correctly
          range.selectNodeContents(element);

          selection.removeAllRanges();
          selection.addRange(range);
        }

        try {
          document.execCommand("copy");
        } catch (err) {
          console.error("Unable to copy text: ", err);
        } finally {
          if (selection) {
            selection.removeAllRanges();
          }
        }
      }
Lection answered 29/5 at 13:26 Comment(0)
S
0

Adapting @weirdpanda's excellent example to modern HTML APIs, you get the following: if you run the example and click the "Copy" button and paste into a spreadsheet program like Excel, you'll get the same beige-and-gray table (with "2.0" possibly unhelpfully rendered as "2" 🙃).

And this will continue to work if you put this in an HTML file and serve it over plain HTTP—but caveat, for an accurate test, you must host it on a non-localhost HTTP URL because your browser will probably give localhost a secure context.

document.querySelector("button").onclick = () => {
  const target = document.querySelector("table");
  copyElement(target);
};

function copyElement(target) {
  const range = document.createRange();

  range.setStart(target, 0);
  range.setEndAfter(target);
  window.getSelection()?.removeAllRanges();
  window.getSelection()?.addRange(range);

  document.execCommand("copy");
  
  window.getSelection()?.removeAllRanges();
};
table {
  border: 1px solid black;
  border-collapse: collapse;
}
thead {
  background-color: gray;
  font-weight: bold;
}
tbody tr:nth-child(even) {
  background-color: lightgray;
}
tbody tr:nth-child(odd) {
  background-color: beige;
}
td {
  border: 1px solid black;
}
<button>Copy</button>

<table>
  <thead>
    <tr>
      <td>Name</td>
      <td>Type</td>
    </tr>
  </thead>
  <tbody>
    <tr><td>Murderbot</td><td>SecUnit</td></tr>
    <tr><td>ART</td><td>Research transport</td></tr>
    <tr><td>Amena</td><td>Human</td></tr>
    <tr><td>Tlacy</td><td>Augmented human</td></tr>
    <tr><td>2.0</td><td>Sentient virus</td></tr>
  </tbody>
</table>
Scum answered 15/11, 2023 at 16:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.