The Definitive Guide to Copying and Pasting in JavaScript

At Lucid Software, we strive to build desktop quality software that runs in the browser. Until recently, however, we had settled for using our own internal clipboard implementation rather than the system clipboard — limiting copying and pasting with external applications to just plain text. With customers clamoring for the ability to paste images captured from screenshots or copied from other browser windows, we decided to use the system clipboard exclusively, thus opening the doors to rich content addition.

Along the way, we encountered three major challenges: limited clipboard access, inconsistent events across browsers, and Internet Explorer limited support of multiple data types. This blog post will discuss what we learned and how you can put our insights to use. Keep reading to see code snippets and in-depth answers to these problems. If you have questions, just let us know. We’ll be happy to add more details.

Step #1 — Access Clipboard Events on Any Browser

Clipboard Access Prompt in Internet Explorer

There are several security issues with letting a web page access the system clipboard. Because of this, browsers limit access to the clipboard. In general, you can only access the clipboard during a system cut, copy, or paste event. These are fired when a user presses the keyboard shortcuts or uses the browser’s menu. This is limiting for two reasons:

  1. Browsers (with the exception of Chrome) only fire clipboard events when there is a valid selection and focus on HTML elements. To see this for yourself, use Chrome to play with the following code.
    ['cut', 'copy', 'paste'].forEach(function(event) {
        document.addEventListener(event, function(e) {
            console.log(event);   
        });
    });
                

    Now try it in another browser. Nothing. The events don’t fire. Now try adding a text input field. If your text cursor is in the input field and you have some text on the clipboard, it pastes just fine. To get copy or cut to fire, you’ll need to have some text selected in the input area.

    In Lucidchart, we don’t use HTML elements to handle or render our text or shapes. To access the clipboard, we need a clipboard event. To get a clipboard event, we need the focused HTML elements the browser expects. But we don’t use those HTML elements.

  2. We would like to support copy and paste from our context menu when a user right-clicks, but since we use our own context menu and not the browser’s, the system clipboard event isn’t fired. And that means we don’t get access to the system clipboard.

Consistently Getting Clipboard Events

Our solution looks something like the following code:

var hiddenInput = $('#hidden-input');

var focusHiddenArea = function() {
    hiddenInput.val(' ');
	hiddenInput.focus().select();
};

$(document).mouseup(focusHiddenArea);

['cut', 'copy', 'paste'].forEach(function(event) {
    document.addEventListener(event, function(e) {
        console.log(event);
        focusHiddenArea();
        e.preventDefault();
    });
});

We have a hidden text area that is always set to have some text selected. This way, the cut, copy, and paste events are always fired in any browser. However, you’ll notice that this Fiddle doesn’t do anything when the user types—and after the user types, the clipboard events won’t fire until the hidden text area is refocused by clicking. In the case of an application like Lucidchart, this is an insufficient solution. We can’t just throw away user keyboard input.

I won’t go into all of the details with handing our input, because it turns out that implementing your own text editing is a rather detailed problem. But just to give you an idea, this fiddle is able to successfully capture input and remain ready for a cut, copy, or paste event.

Context Menu Copying and Pasting

So far, we only support context menu copying and pasting for Chrome and IE. Users of other browsers are limited to keyboard shortcuts. Chrome can grant us clipboard access via our Chrome extension, and Internet Explorer lets you access the clipboard any time (via window.clipboardData), but will prompt the user if it is outside the system event. Other than those two cases, we just can’t support menu copying and pasting (though you’ll find plenty of people trying to get around this, the only real way I know of is to is to use Flash) . Our only consolation is that Google can’t do it either. Go ahead and try it with Google Docs. It only works in Chrome and IE. Use it in Firefox or Safari and you’ll notice a nice little dialog telling you to use keyboard shortcuts.

Limited clipboard acces notice

Step #2 — Get Data in Multiple Formats To and From the Clipboard

We now have a reliable way of receiving clipboard events. Now we’ll address the problem of moving data to and from it.

Let’s start by describing what we want. Desktop applications can read and write any type of data on the clipboard. If it can be represented as a stream of bytes, it can go on the clipboard. For example, if I select multiple shapes and text areas in Microsoft PowerPoint, then paste into Microsoft Word, all the shapes and text are correctly pasted using some Object representation of shapes and text internal to MS Office. What’s more, the application can put multiple data forms onto the clipboard. So, those shapes from PowerPoint can likewise be pasted into Photoshop or Gimp as an image. When you copy in PowerPoint, something like this probably happens.


clipboard.setData('text/plain', selection.getText());
clipboard.setData('application/officeObj’, selection.serialize());
clipboard.setData('image/bmp', draw(selection));
clipboard.setData('text/html', ...);
...

PowerPoint is going to try to copy every possible useful data type to the clipboard so that other applications can use whatever data type they recognize.

On the web, we don’t have quite the same flexibility. While the standards describe a minimum support of a wide number of data types, we tend to be happy if we can just consistently access plain text on the clipboard. Right now, here is what the browsers actually support:

  • Chrome and Safari: They support any content type on the clipboardData, including custom types. So, we can call clipboardData.setData('application/lucidObjects', serializedObjects) for pasting, and then call var serialized = clipboardData.getData('application/lucidObjects')
  • Firefox: It currently only allows access to the data types described above. You can set custom types on copy, but when pasting, only the white-listed types are passed through.
  • Internet Explorer: In true IE fashion, it supports just two data types: Text and URL. Oh, and if you set one, you can’t set the other (it gets nulled out). There is a hack, however, that also allows us to indirectly get and set HTML.

The clipboard object in Internet Explorer doesn’t expose text/html via JavaScript. It does, however, support copying and pasting HTML into contenteditable elements. We can leverage this if we let the browser perform its default copy and paste, but ‘hijack’ the events to get/put the HTML data we want. This would look something like the following code.


if (isIe) {
    document.addEventListener('beforepaste', function() {
        if (hiddenInput.is(':focus')) {
            focusIeClipboardDiv();
        }
    }, true);
}

var ieClipboardEvent = function(clipboardEvent) {
    var clipboardData = window.clipboardData;
    if (clipboardEvent == 'cut' || clipboardEvent == 'copy') {
        clipboardData.setData('Text', textToCopy);
        ieClipboardDiv.html(htmlToCopy);
        focusIeClipboardDiv();
        setTimeout(function() {
            focusHiddenArea();
            ieClipboardDiv.empty();
        }, 0);
    }
    if (clipboardEvent == 'paste') {
        var clipboardText = clipboardData.getData('Text');
        ieClipboardDiv.empty();
        setTimeout(function() {
            console.log('Clipboard Plain Text: ' + clipboardText);
            console.log('Clipboard HTML: ' + ieClipboardDiv.html());
            ieClipboardDiv.empty();
            focusHiddenArea();
        }, 0);
    }
};

Below is a diagram illustrating the entire process:


Diagram illustrating clipboard functionality in Lucidchart

Here’s what’s going on:

  • We have a hidden contenteditable div on the page just for copy and paste.
  • During the copy event (before the system performs its default action), we set the div’s HTML to whatever we want placed on the clipboard and programmatically select the entire content.
  • Then when the system performs copy, it will get the desired HTML and it will be put on the system clipboard.
  • To paste HTML, we shift the focus to the contenteditable element during the beforepaste event.
  • Immediately after the system has performed its default paste, we just extract and clear the pasted HTML.

This final snippet gives a working example of how we copy and paste custom text and HTML across browsers using nothing but JavaScript. This is actually enough to support everything our old clipboard implementation was doing. We encode/decode our shapes to/from HTML  so we can copy and paste shapes in the editor, and we get/set plain text on the clipboard so that we can copy and paste text to and from other applications.

Step #3 — Have Fun

Obviously, we didn’t go through this mess solely to keep our previous functionality. Now that we have consistent access to the system clipboard, we are no longer limited to living entirely within our own application. Rather, Lucidchart users can share and receive data from other applications by using the system clipboard. For instance, it was an easy process to add the functionality for pasting images from a desktop screenshot or from another browser window. And we also now support the pasting of formatted text from Gmail, Google Docs, or other web pages.

We’d really love to hear what you think about this feature. Go ahead and try it for yourself!
If you have any ideas to improve it — or just want to commiserate about copying and pasting the web — do not hesitate to leave feedback on our support forums.

Useful resources

24 Comments

  1. There is an issue with the hidden textarea jsfiddle (http://jsfiddle.net/fpm1nn85/). When we focus on the textarea, the scrollbar goes to the bottom. Solved this by changing the `hidden` class: `position: fixed; top: 0; left: 0`. That way on focus scrollbar does not change.

  2. Hi, the final snippet you posted here is supposed to be able to do copy and paste html for all browsers yea? But it doesnt paste html on firefox. Why is that?
    Is it not supposed to paste html in firefox? but in the article here it says that firefox can indeed paste html

  3. I’m having a really hard time to copy the canvas into the clipboardData object and use it in elsewhere (let’s say MS Outlook message). All the samples demonstrate using the “text/plain” or “text/html” strings which works fine for me as well, but there are no sample on how to let’s say copy the image or canvas.

    what I’ve tried so far was :
    1) convert the canvas to dataURI, convert this to blob, and convert the blob to file and I’ve tried to clipboardData.items.setData( “image/png”, file), or clipboardData.items.files.add(f) but no luck there

    2) I’ve tried to setData(“image/png”, blob)

    3) I’ve tried to setData(“image/png”, stream) where stream is a blob converted into a UInt8Array

    and many other things… any ideas how to get a simple image let’s say this one “data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMvvhp8YAAAA3SURBVDhPY/hIAaCG5lwkQLzI0NZMHqCGZqhrwIB4kaGtmTxADc1Q14AB8SJDWzN5gALNHz8CAJNDix6/DGcSAAAAAElFTkSuQmCC” to clipboard and paste it in word ?

    thanks

  4. Dmitry PashkevichJuly 20, 2015 at 11:06 am

    Unfortunately, browsers do not currently support putting image data onto the clipboard. It is something we wanted to do for Lucidchart and Lucidpress, but it doesn’t appear to be possible. Chrome doesn’t support is (there an issue for it already for it in the Chromium project (feel free to up-vote it)), and Chrome has had the best clipboard support of any of the browsers, so I assume it is the same for other browsers.

    With that said, we did find a creative way to use HTML to put images on the clipboard. Most productivity applications (MS Word, Powerpoint, etc) will download any images contained in the HTML that is pasted into the application. So, we created an endpoint on our servers to retrieve image data, and then just generate tags pointing to that endpoint with the query parameters specifying the part of the document to generate as an image. When the user pastes the HTML with the tag, MS Word (or whatever application) makes the call to download the image. Our servers generate the image as specified, and to the user, it looks as if they are pasting an image. The solution isn’t without its flaws, but it is the best we can do until browsers support copying image data to the clipboard.

  5. Hi,
    Thank you for the detailed information and jsFiddle.

    I have a dataURL of a HTML5 canvas element generated by canvas.toDataURL(). How can I copy this dataURL to the clipboard so that I can paste it as an image in other applications?

    dataURL has the following format:
    “data:image/png;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/……”

    clipboardData.setData(‘image/png’, dataURL); doesn’t appear to work.

    Any suggestions? Thanks.

  6. Richard ShurtzSeptember 22, 2015 at 7:38 pm

    As was responded before… “Unfortunately, browsers do not currently support putting image data onto the clipboard. It is something we wanted to do for Lucidchart and Lucidpress, but it doesn’t appear to be possible. Chrome doesn’t support it (there an issue for it already for it in the Chromium project (feel free to up-vote it)), and Chrome has had the best clipboard support of any of the browsers, so I assume it is the same for other browsers.”

  7. Hi Richard,
    Thank you repeating the response. After I posted my question, I realized that someone had already posted the same question where Dmitry responded to it.

  8. STOP SCREWING WITH MY COPY/PASTE. There are websites where I can’t paste text into a textbox (like facebook), and the keyboard shortcuts don’t work, but the menu does. Or when I submit, its just blank. I don’t need all this extra junk. Stuff used to just “work” and now developers are trying to show their managers flashy stuff that just makers the users experience suffer.

  9. Thanks very much for this helpfull writeup on the topic! You make this world a better place. Thanks very much for that, saved me lots of time to resarch this also. All the best!

  10. Hi Richard,

    We have been using a desktop html editor of our own design. We typically paste Word docs into our editor, and we run a conversion of rtf to html. Because the editor has been a desktop app, we have been able to access the system clipboard and get the pasted data in rtf format:

    System.Windows.Forms.clipboardRtf = Clipboard.GetData(System.Windows.Forms.DataFormats.Rtf)

    We have a web-based version our editor, and now that we don’t have access to the user’s system clipboard, we are exploring methods for still getting rtf format, instead of text or html. Any ideas how we can get rtf when a Word doc is copied to a user’s clipboard and then pasted into an app in a browser?

    Thanks.

    Richard

  11. Richard ShurtzMarch 14, 2016 at 2:17 pm

    I’ve tried playing around with this to see if there is anyway to get RFT data off the clipboard in the browser. As far as I can tell, there is not. What is interesting, however, is if you copy from Word, and paste in Chrome (using one of the snippets linked to in the post above) and then inspect the clipboardData object, there is a DataTransferItem of type ‘text/rtf’. Unfortunately, whenever I have called clipboardData.getData(‘text/rtf’), I only get back the empty string. So, for some reason, Chrome recognizes there is some RTF data on the clipboard, but doesn’t (for whatever reason) actually expose it.

  12. Thanks a lot for this tutorial, I was looking for something similar for Libgdx game.
    I’ll refer to this site as reference to my answer on stackoverflow.
    http://stackoverflow.com/questions/36941236/libgdx-html-clipboard

  13. Your jsfiddle links are broken 🙁

  14. Richard ShurtzAugust 12, 2016 at 2:57 pm

    I just clicked through all the links on the page and they are all working. Maybe jsfiddle was down temporarily…

  15. Your jsFiddle has an HTML5 canvas demo. Not the actual clipboard demo.

  16. Richard ShurtzJanuary 17, 2017 at 9:28 am

    Caleb – The jsFiddle demo has an HTML5 canvas to mimic what we use in Lucidchart and Lucidpress. However, it also contains the clipboard code. To see what the clipboard is doing, just open the dev console. Hope that helps!

  17. Hi!
    Thank you for the article!

    Question: How in IE to insert in setData not text, but html?

  18. Richard ShurtzApril 7, 2017 at 10:37 am

    Sergey – I explained the method to get and set HTML in IE (I event included a diagram to help explain it as it can be tricky to follow) in the post above. Here is the link to the code snippet that should be a good starting place for you as you work on it. http://jsfiddle.net/vtjnr6ea/

  19. […] References: THE DEFINITIVE GUIDE TO COPYING AND PASTING IN JAVASCRIPT […]

  20. Carlos MauricioSeptember 24, 2017 at 10:29 pm

    Es posible hacer lo dicho anteriormente con un menú personalizado (“contaxtmenu”) al dacr click derecho sobre cajas de texto (inpus), con la opción de: seleccionar, copiar, cortar, pegar y eliminar. Que corra en los navegadores.

  21. […] The Definitive Guide to Copying and Pasting in JavaScript […]

  22. I like the valuable info you provide for your articles.
    I will bookmark your blog and take a look at again here frequently.
    I am reasonably sure I’ll learn many new stuff right here!
    Good luck for the following!

  23. Hello Richard
    I am implementing the copy/paste feature in react. I need to use htmlToCanvas and then generate canvas.toDataURL () and then set clipboard data with canvas.toDataURL (). canvas.toDataURL () takes time so the clipboard data is not set accordingly. Will you please guide me?.

Your email address will not be published.