112

Some websites now use a JavaScript service from Tynt that appends text to copied content.

If you copy text from a site using this and then paste you get a link to the original content at the bottom of the text.

Tynt also tracks this as it happens. It's a neat trick well done.

Their script for doing this is impressive - rather than try to manipulate the clipboard (which only older versions of IE lets them do by default and which should always be turned off) they manipulate the actual selection.

So when you select a block of text the extra content is added as a hidden <div> included in your selection. When you paste the extra style is ignored and the extra link appears.

This is actually fairly easy to do with simple blocks of text, but a nightmare when you consider all the selections possible across complex HTML in different browsers.

I'm developing a web application - I don't want anyone to be able to track the content copied and I would like the extra info to contain something contextual, rather than just a link. Tynt's service isn't really appropriate in this case.

Does anyone know of an open source JavaScript library (maybe a jQuery plug in or similar) that provides similar functionality but that doesn't expose internal application data?

AstroCB
  • 12,337
  • 20
  • 57
  • 73
Keith
  • 150,284
  • 78
  • 298
  • 434
  • 1
    Have a look at my answer at http://stackoverflow.com/questions/6344588/how-does-financial-times-add-a-disclaimer-when-pasting-text/6347927#6347927. It is done very similarly as you proposed – Niklas Jun 14 '11 at 18:13
  • 1
    See also http://stackoverflow.com/questions/1203082/injecting-text-when-content-is-copied-from-a-web-page – Gnubie Apr 14 '14 at 18:10
  • 56
    Please don't do this. PLEASE PLEASE PLEASE just don't. – couchand Jun 30 '15 at 13:49
  • 6
    @couchand why not? I get how annoying this is on spam sites, but this is for an application that can be used for citations and where the internal data is sensitive. That's why I didn't want to use Tynt. – Keith Jun 30 '15 at 14:29
  • 2
    Alternative: https://github.com/tovic/sticky-attribution – Taufik Nurrohman May 29 '17 at 03:27
  • 6
    Are you sure you want to do this? As a user, I hate it and I will port this anger into your product: [Don't touch my clipboard!](https://alexanderell.is/posts/taking-over-my-clipboard/) – aloisdg Feb 18 '20 at 09:38
  • 3
    @aloisdgmovingtocodidact.com this was a decade ago, but it was a requirement - the documents they were copying from were legal and it was a big help for them to automatically include the citation link back to the source. This isn't something I'd do on general sites - the users understood that the copied quote had to cite exactly where it came from. – Keith Feb 18 '20 at 12:44

8 Answers8

159

2022 Update

More complex solution that handles rich text formatting. The 2020 solution is still relevant if you only deal with plain text.

const copyListener = (event) => {
  const range = window.getSelection().getRangeAt(0),
    rangeContents = range.cloneContents(),
    pageLink = `Read more at: ${document.location.href}`,
    helper = document.createElement("div");

  helper.appendChild(rangeContents);

  event.clipboardData.setData("text/plain", `${helper.innerText}\n${pageLink}`);
  event.clipboardData.setData("text/html", `${helper.innerHTML}<br>${pageLink}`);
  event.preventDefault();
};
document.addEventListener("copy", copyListener);
#richText {
  width: 415px;
  height: 70px;
  border: 1px solid #777;
  overflow: scroll;
}

#richText:empty:before {
  content: "Paste your copied text here";
  color: #888;
}
<h4>Rich text:</h4>
<p>Lorem <u>ipsum</u> dolor sit <b>amet</b>, consectetur <i>adipiscing</i> elit.</p>
<h4>Plain text editor:</h4>
<textarea name="textarea" rows="5" cols="50" placeholder="Paste your copied text here"></textarea>
<h4>Rich text editor:</h4>
<div id="richText" contenteditable="true"></div>

2020 Update

Solution that works on all recent browsers.

Note that this solution will strip rich text formatting (such as bold and italic), even when pasting into a rich text editor.

document.addEventListener('copy', (event) => {
  const pagelink = `\n\nRead more at: ${document.location.href}`;
  event.clipboardData.setData('text/plain', document.getSelection() + pagelink);
  event.preventDefault();
});
Lorem ipsum dolor sit <b>amet</b>, consectetur <i>adipiscing</i> elit.<br/>
<textarea name="textarea" rows="7" cols="50" placeholder="paste your copied text here"></textarea>

[Older post - before the 2020 update]

There are two main ways to add extra info to copied web text.

  1. Manipulating the selection

The idea is to watch for the copy event, then append a hidden container with our extra info to the dom, and extend the selection to it.
This method is adapted from this article by c.bavota. Check also jitbit's version for more complex case.

  • Browser compatibility: All major browsers, IE > 8.
  • Demo: jsFiddle demo.
  • Javascript code:

    function addLink() {
        //Get the selected text and append the extra info
        var selection = window.getSelection(),
            pagelink = '<br /><br /> Read more at: ' + document.location.href,
            copytext = selection + pagelink,
            newdiv = document.createElement('div');

        //hide the newly created container
        newdiv.style.position = 'absolute';
        newdiv.style.left = '-99999px';

        //insert the container, fill it with the extended text, and define the new selection
        document.body.appendChild(newdiv);
        newdiv.innerHTML = copytext;
        selection.selectAllChildren(newdiv);

        window.setTimeout(function () {
            document.body.removeChild(newdiv);
        }, 100);
    }

    document.addEventListener('copy', addLink);
  1. Manipulating the clipboard

The idea is to watch the copy event and directly modify the clipboard data. This is possible using the clipboardData property. Note that this property is available in all major browsers in read-only; the setData method is only available on IE.

  • Browser compatibility: IE > 4.
  • Demo: jsFiddle demo.
  • Javascript code:

    function addLink(event) {
        event.preventDefault();

        var pagelink = '\n\n Read more at: ' + document.location.href,
            copytext =  window.getSelection() + pagelink;

        if (window.clipboardData) {
            window.clipboardData.setData('Text', copytext);
        }
    }

    document.addEventListener('copy', addLink);
CronosS
  • 3,129
  • 3
  • 21
  • 28
  • 1
    Cheers! Unfortunately we need it working in IE, but that isn't a bad start. – Keith Jan 24 '11 at 12:35
  • @Keith did You find solution both for IE and normal browsers? Without Flash solution. – tunarob Jun 06 '12 at 13:42
  • 2
    There should be a workaround for "
    " tags, a smoother version of this script is [here](http://www.jitbit.com/alexblog/230-javascript-injecting-extra-info-to-copypasted-text/)
    – Alex from Jitbit Dec 24 '13 at 10:09
  • This doesn't work for me in Yosemite. Once I mess with `selection` in the copy handler in any way, nothing gets copied at all in Chrome/Firefox (the previous value remains in the clipboard); in Safari the clipboard is cleared but nothing new appears there. – mgol Dec 10 '14 at 21:02
  • EDIT: oh, it works but the element cannot have `visibility: hidden` set. Weird. – mgol Dec 10 '14 at 21:08
  • 15
    Note that "Manipulating the clipboard" works prefectly in FireFox, Chrome and Safari if you change `window.clipboardData` to `event.clipboardData`. IE (v11 too) don't support `event.clipboardData` http://jsfiddle.net/m56af0je/8/ – mems Dec 12 '14 at 10:22
  • 3
    If you are using Google Analytics etc, you can even fire off an event to log what users are copying from your site. Interesting – geedubb Jan 05 '15 at 15:34
  • 2
    The first option ignores the new line characters of the copied text. – soham Jan 30 '15 at 20:59
  • Approach 1 on Edge works but has a graphical defect. When you copy, the line `selection.selectAllChildren(newdiv);` makes your page scroll to the bottom. This happens because the `newdiv` element is appended to the bottom. For avoiding the unwanted scroll, append the `newdiv` to the element that triggered the copy (jquery): `$(e.target).after($(newdiv))` – reallynice Apr 14 '16 at 08:47
  • It's kind of disturbing to be able to do this but not being able to do it with a simpler function. – Alexander May 16 '17 at 19:32
  • @geedubb how can we fire off an event to log what users aer copying from the site? – Antonio D. Mar 31 '20 at 09:53
  • Can we have the text inserted when just more than a certain number of characters are copied? – Mert S. Kaplan Jul 05 '22 at 16:16
  • 1
    typo: `e` should be `event` – shtse8 Sep 20 '22 at 17:41
8

This is a vanilla javascript solution from a modified solution above but supports more browsers (cross browser method)

function addLink(e) {
    e.preventDefault();
    var pagelink = '\nRead more: ' + document.location.href,
    copytext =  window.getSelection() + pagelink;
    clipdata = e.clipboardData || window.clipboardData;
    if (clipdata) {
        clipdata.setData('Text', copytext);
    }
}
document.addEventListener('copy', addLink);
GiorgosK
  • 7,647
  • 2
  • 29
  • 26
3

The shortest version for jQuery that I tested and is working is:

jQuery(document).on('copy', function(e)
{
  var sel = window.getSelection();
  var copyFooter = 
        "<br /><br /> Source: <a href='" + document.location.href + "'>" + document.location.href + "</a><br />© YourSite";
  var copyHolder = $('<div>', {html: sel+copyFooter, style: {position: 'absolute', left: '-99999px'}});
  $('body').append(copyHolder);
  sel.selectAllChildren( copyHolder[0] );
  window.setTimeout(function() {
      copyHolder.remove();
  },0);
});
  • where is the code which actually copies the result to the clipboard? – vsync Oct 27 '16 at 22:43
  • @vsync I believe this just adds functionality just before the copying takes place (which is done by the system when the user initiates it). – TerranRich Apr 13 '18 at 17:26
  • @vsync - as TerraRich said, I tried to respond for the question, which was about adding extra info into copied text, so solution covers this part only. – user2276146 Sep 24 '19 at 08:58
3

Here is a plugin in jquery to do that https://github.com/niklasvh/jquery.plugin.clipboard From the project readme "This script modifies the contents of a selection prior to a copy event being called, resulting in the copied selection being different from what the user selected.

This allows you to append/prepend content to the selection, such as copyright information or other content.

Released under MIT License"

sktguha
  • 524
  • 3
  • 21
  • 1
    That looks very promising. It uses inline styles which we don't allow with our CSP, but it could potentially be adapted. Cheers! – Keith Sep 08 '15 at 17:46
3

Improving on the answer, restore selection after the alterations to prevent random selections after copy.

function addLink() {
    //Get the selected text and append the extra info
    var selection = window.getSelection(),
        pagelink = '<br /><br /> Read more at: ' + document.location.href,
        copytext = selection + pagelink,
        newdiv = document.createElement('div');
    var range = selection.getRangeAt(0); // edited according to @Vokiel's comment

    //hide the newly created container
    newdiv.style.position = 'absolute';
    newdiv.style.left = '-99999px';

    //insert the container, fill it with the extended text, and define the new selection
    document.body.appendChild(newdiv);
    newdiv.innerHTML = copytext;
    selection.selectAllChildren(newdiv);

    window.setTimeout(function () {
        document.body.removeChild(newdiv);
        selection.removeAllRanges();
        selection.addRange(range);
    }, 100);
}

document.addEventListener('copy', addLink);
Serg
  • 6,742
  • 4
  • 36
  • 54
digitalPBK
  • 2,859
  • 25
  • 26
3

Improvement for 2018

document.addEventListener('copy', function (e) {
    var selection = window.getSelection();
    e.clipboardData.setData('text/plain', $('<div/>').html(selection + "").text() + "\n\n" + 'Source: ' + document.location.href);
    e.clipboardData.setData('text/html', selection + '<br /><br /><a href="' + document.location.href + '">Source</a>');
    e.preventDefault();
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<p>Example text with <b>bold</b> and <i>italic</i>. Try copying and pasting me into a rich text editor.</p>
Flimm
  • 136,138
  • 45
  • 251
  • 267
tronic
  • 459
  • 2
  • 11
0

Also a little shorter solution:

jQuery( document ).ready( function( $ )
    {
    function addLink()
    {
    var sel = window.getSelection();
    var pagelink = "<br /><br /> Source: <a href='" + document.location.href + "'>" + document.location.href + "</a><br />© text is here";
    var div = $( '<div>', {style: {position: 'absolute', left: '-99999px'}, html: sel + pagelink} );
    $( 'body' ).append( div );
    sel.selectAllChildren( div[0] );
    div.remove();
    }



document.oncopy = addLink;
} );
almo
  • 6,107
  • 6
  • 43
  • 86
0

It's a compilation of 2 answers above + compatibility with Microsoft Edge.

I've also added a restore of the original selection at the end, as it is expected by default in any browser.

function addCopyrightInfo() {
    //Get the selected text and append the extra info
    var selection, selectedNode, html;
    if (window.getSelection) {
        var selection = window.getSelection();
        if (selection.rangeCount) {
            selectedNode = selection.getRangeAt(0).startContainer.parentNode;
            var container = document.createElement("div");
            container.appendChild(selection.getRangeAt(0).cloneContents());
            html = container.innerHTML;
        }
    }
    else {
        console.debug("The text [selection] not found.")
        return;
    }

    // Save current selection to resore it back later.
    var range = selection.getRangeAt(0);

    if (!html)
        html = '' + selection;

    html += "<br/><br/><small><span>Source: </span><a target='_blank' title='" + document.title + "' href='" + document.location.href + "'>" + document.title + "</a></small><br/>";
    var newdiv = document.createElement('div');

    //hide the newly created container
    newdiv.style.position = 'absolute';
    newdiv.style.left = '-99999px';

    // Insert the container, fill it with the extended text, and define the new selection.
    selectedNode.appendChild(newdiv); // *For the Microsoft Edge browser so that the page wouldn't scroll to the bottom.

    newdiv.innerHTML = html;
    selection.selectAllChildren(newdiv);

    window.setTimeout(function () {
        selectedNode.removeChild(newdiv);
        selection.removeAllRanges();
        selection.addRange(range); // Restore original selection.
    }, 5); // Timeout is reduced to 10 msc for Microsoft Edge's sake so that it does not blink very noticeably.  
}

document.addEventListener('copy', addCopyrightInfo);
Serg
  • 6,742
  • 4
  • 36
  • 54