37

I'm trying to write a little bookmarklet that can extract some text from the active page and load that into the clipboard.

The extraction is easy enough, but I'm really stuck doing the clipboard-copying part. Currently, I'm just alerting the text and hitting Ctrl+C to copy the text from the message-box, which isn't ideal.

I've read How to Copy to Clipboard in JavaScript and other questions that suggest I use zeroclipboard, but I have no idea how one would make that work from a bookmarklet, considering I have to load external flash and javascript resources to be able to use the library.

I have no issues with messing up the page's DOM to accomplish this or having to enable some permissions on my browser (Google Chrome), considering this is just a private bookmarklet.

Any pointers would be appreciated.

0

7 Answers 7

27

Since the new Clipboard API, this is easy in browsers that support it:

javascript: navigator.clipboard.writeText('some text from somewhere');null;

Caveat: Any alerts or prompts in your bookmarklet will cause the document to lose focus, and the clipboard API will become unavailable. In that case you need to move focus back into the document before any subsequent clipboard operations will work:

const oldFocus = document.activeElement; 
/* do popup stuff here */
oldFocus.focus(); 
/* do clipboard stuff here */
6
  • 3
    'some text from somewhere' is so much more serious that the otherwise usual "bla blah bal" which would be used in an example.
    – dotbit
    Commented May 20, 2020 at 19:21
  • 4
    What is the purpose of null; in ;null; ? Commented Oct 24, 2020 at 0:16
  • 3
    Also, since this is for a bookmarklet, everything is "compressed" (joined) to one line, so using // in a bookmarklet is a problem. I think /* comment */ should work fine. Commented Oct 24, 2020 at 0:20
  • 1
    @kevin-fegan Bookmarklets need to return something falsy, unless you want the browser to replace the current page with the return value. This particular code already does, but it is standard practice to always explicitly null the return value so you are safe from any changes triggering that trap. There are many ways to ensure this - I prefer returning an explicit null or false. Commented Oct 31, 2020 at 11:18
  • 4
    @netawater - I'm pretty sure that JoachimLous just meant that you should be careful if your bookmarklet code opens any alerts or "Popups" (like: window.alert("sometext");, alert("sometext");, window.confirm("sometext");, confirm("sometext");, window.prompt("sometext","defaultText"); or prompt("sometext","defaultText");), Before your code that writes to the clipboard. If you don't use alerts or prompts, or if you use them After your code that writes to the clipboard, you don't (shouldn't) have to worry about restoring the focus, you can ignore the ....focus(); stuff. Commented Jan 5, 2021 at 23:00
22

There is a nice little bookmarket in Github Gist that does the core of what you want -- copying to the clipboard. It does not use any external libraries, which I think of as an advantage.

As written, it copies some static text, but toward the bottom it talks about adapting it to other uses, such as copying the page title.

Since you've stated that 'The extraction is easy enough ...', you should be easily be able to adapt that gist to what you want to do.

I tried the plain vanilla version of the bookmarklet, because I have some static text that I often need to transfer to my clipboard. It works very well in Chrome 61 with no modifications. But make sure you read the comments; some people have suggestions on getting it to work in other browsers and scenarios.

Here is the code that I tested, already minified and ready to turn into a bookmarklet:

javascript:!function(a){var b=document.createElement("textarea"),c=document.getSelection();b.textContent=a,document.body.appendChild(b),c.removeAllRanges(),b.select(),document.execCommand("copy"),c.removeAllRanges(),document.body.removeChild(b)}("Text To Copy");

Gist has the pre-minified code as well.

2
  • 3
    Apparently, this does not work in Firefox: document.execCommand(‘cut’/‘copy’) was denied because it was not called from inside a short running user-generated event handler.
    – Perseids
    Commented Aug 3, 2020 at 5:06
  • 2
    Thank you @Perseids. Mark's answer provides more information on how to deal with Firefox. Also the original Gist this was based on has a comment or two that may help.
    – Gundark
    Commented Jul 2, 2022 at 0:09
12

With recent versions of Firefox, interacting with the clipboard via a bookmarklet in general won't work due to missing permissions (see this information for more details). There may be a way, however, to have the bookmarklet display a button, and perform the clipboard interaction in the context of a button-click event handler.

A possibly more straightforward solution is to use a user-script manager, and define your bookmarklet in the form of a user-script that you can activate via a keyboard combination. See, for example this user script, reproduced here for completeness:

// Add the following as a user-script (via an extension like https://github.com/violentmonkey/violentmonkey) in order to copy the
// current webpage and selected text to the clipboard in a format suitable for pasting into an org-mode document.
// To execute the action, you need to press Alt-C on a webpage, though this can be modified by changing the keycode
// used in the onkeyup function.

// ==UserScript==
// @name Copy Org-mode Link
// @namespace Violentmonkey Scripts
// @match *://*/*
// @grant clipboardWrite
// ==/UserScript==

function main() {
    function copyTextToClipboard(text) { var textArea = document.createElement("textarea"); textArea.style.position = 'fixed'; textArea.style.top = 0; textArea.style.left = 0; textArea.style.width = '2em'; textArea.style.height = '2em'; textArea.style.padding = 0; textArea.style.border = 'none'; textArea.style.outline = 'none'; textArea.style.boxShadow = 'none'; textArea.style.background = 'transparent'; textArea.value = text; document.body.appendChild(textArea); textArea.select(); try { var successful = document.execCommand('copy'); var msg = successful ? 'successful' : 'unsuccessful'; console.log('Copying text command was ' + msg); } catch (err) { console.log('Oops, unable to copy'); } document.body.removeChild(textArea); }; var url = encodeURIComponent(location.href); url = url.replace(/%253A/g, ':').replace(/%252F/g, '/'); var title = document.title; title = title.replace(/\[/g, '{'); title = title.replace(/\]/g, '}'); var sel_text = window.getSelection(); copyTextToClipboard('[['+url+']['+title+']]'+'\n\n'+sel_text);
}

// listen for Alt-C key-combination, and then execute
document.onkeyup=function(e){
    var e = e || window.event; // for IE to cover IEs window object
    if(e.altKey && e.which == 67) {
         main();
         return false;
    }
}
5

An answer that's a bit unusual: open a blank page from which the user will copy the text:

<a href="javascript:window.open('data:text/html, <html contenteditable>sup<script>document.execCommand(\'selectAll\')</script></html>')">
  Copy the text “sup”
</a>

Just replace sup by the text you want the user to copy.

JS Bin example

1
  • 4
    handy, thanks! note: some quotes need escaping: execCommand(\'selectAll\')
    – ptim
    Commented Jan 2, 2015 at 6:29
4

Here's how I solved it, using the technique @zzzzBov mentioned in his answer, to import zeroclipboard into the page via a bookmarklet.

When the bookmarket runs, a hand cursor appears on hovering over anywhere on the body. Clicking will copy (for example) the document's title to the clipboard.

(Links to zeroclipboard resources have been replaced with placeholders, and multi-line comments have been used since Chrome appears to be removing all line-breaks from bookmarklets (or something))

javascript:

var s = document.createElement('script');
s.setAttribute('src', 'http://example.com/ZeroClipboard.js');

s.onload = s.onreadystatechange = 
  function()
  { 
     ZeroClipboard.setMoviePath( 'http://example.com/ZeroClipboard.swf');
     var clip = new ZeroClipboard.Client();   

     /* glue to the body: sample only, in reality  we should
        probably create a new visible element and glue to that. */
     clip.glue(document.body);   

     clip.setHandCursor( true );

     /* copy to clipboard on mouse-up */
     clip.addEventListener('onMouseUp', 
      function (client) 
      {      
         /* example */
         var toCopy = document.title;        
         clip.setText(toCopy);    

         alert(toCopy + ' copied.');
         clip.hide();
      });  
   };

document.body.appendChild(s);
3

A couple disclaimers:

  1. I'm not trying to spam you
  2. I gain nothing if you choose to use this

I made a bookmarklet generator a while back to make it easier for me to create bookmarklets.

It's jQuery enabled, but that doesn't mean you have to use jQuery.

You can check out the source to see how to import another script/library into a page via a bookmarklet.

In particular, the lines that import jQuery:

if (!window.zbooks)
  {
    //if zbooks hasn't been set, initialize it

    //s used for the Script element
    var s = document.createElement('script');
    //r used for the Ready state
    var r = false;
    //set the script to the latest version of jQuery
    s.setAttribute('src', 'http://code.jquery.com/jquery-latest.min.js');
    //set the load/readystate events
    s.onload = s.onreadystatechange = function()
    {
/**
 * LOAD/READYSTATE LOGIC
 * execute if the script hasn't been ready yet and:
 * - the ready state isn't set
 * - the ready state is complete
 *   - note: readyState == 'loaded' executes before the script gets called so
 *     we skip this event because it wouldn't have loaded the init event yet.
 */
      if ( !r && (!this.readyState || this.readyState == 'complete' ) )
      {
        //set the ready flag to true to keep the event from initializing again
        r = true;
        //prevent jQuery conflicts by placing jQuery in the zbooks object
        window.zbooks = {'jQuery':jQuery.noConflict()};
        //make a new zbook
        window.zbooks[n] = new zbooks(c);
      }
    };
    //append the jQuery script to the body
    b.appendChild(s);
  }

I hope that helps.

2
  • Looks useful; will try it out and let you know. Will require a lot of trial and error on my part; I don't know a thing about js.
    – Ani
    Commented Feb 18, 2011 at 22:00
  • 1
    Thanks; that import technique was good enough to set me on my way.
    – Ani
    Commented Feb 21, 2011 at 14:16
1

A solution for both HTTP and HTTPS contexts

The Clipboard API solution by @Joachim Lous was on the right track for me. However, this did not work on localhost, which used http not https. To get around this, I used a "copyToClipboard" function (adapted from this SO answer) which acts as an all-in-one wrapper function that takes into account a http context by using a clever "out of viewport hidden text area" trick.

Readable version:

javascript:

function copyToClipboard(textToCopy) {
    // navigator clipboard api needs a secure context (https)
    if (navigator.clipboard && window.isSecureContext) {
        // navigator clipboard api method
        return navigator.clipboard.writeText(textToCopy);
    } else {
        // use the 'out of viewport hidden text area' trick
        let textArea = document.createElement("textarea");
        textArea.value = textToCopy;
        // make the textarea out of viewport
        textArea.style.position = "fixed";
        textArea.style.left = "-999999px";
        textArea.style.top = "-999999px";
        document.body.appendChild(textArea);
        textArea.focus();
        textArea.select();
        return new Promise((res, rej) => {
            // here the magic happens
            document.execCommand('copy') ? res() : rej();
            textArea.remove();
        });
    }
};
var element = document.querySelector(".myClass");
var text = element.textContent || element.value;
copyToClipboard(text);

Simply replace ".myClass" with your selector.

Minified version for bookmarklet:

javascript:void function(){var a=document.querySelector(".myClass"),b=a.textContent||a.value;(function(a){if(navigator.clipboard&&window.isSecureContext)return navigator.clipboard.writeText(a);else{let b=document.createElement("textarea");return b.value=a,b.style.position="fixed",b.style.left="-999999px",b.style.top="-999999px",document.body.appendChild(b),b.focus(),b.select(),new Promise((a,c)=>{document.execCommand("copy")?a():c(),b.remove()})}})(b)}();

Edit:

The following is a solution for those interested in dynamically selecting text from a page.

javascript:void function(){function a(a){if(navigator.clipboard&&window.isSecureContext)return navigator.clipboard.writeText(a);else{let b=document.createElement("textarea");return b.value=a,b.style.position="fixed",b.style.left="-999999px",b.style.top="-999999px",document.body.appendChild(b),b.focus(),b.select(),new Promise((a,c)=>{document.execCommand("copy")?a():c(),b.remove()})}}function b(a){c.style.pointerEvents="none";var b=document.elementFromPoint(a.clientX,a.clientY);return c.style.pointerEvents="auto",b}var c=document.createElement("div");Object.assign(c.style,{position:"fixed",top:0,left:0,width:"100vw",height:"100vh",zIndex:99999999,background:"transparent",cursor:"crosshair"}),document.body.append(c);document.addEventListener("mousemove",function(a){var d=b(a);if(d){var e=d.getBoundingClientRect();Object.assign(c.style,{background:"rgba(0, 128, 255, 0.25)",outline:"1px solid rgba(0, 128, 255, 0.5)",top:""+e.top+"px",left:""+e.left+"px",width:""+e.width+"px",height:""+e.height+"px"})}}),c.addEventListener("click",function(d){var e=b(d),f=e.textContent||e.value;f=f.replace(/\n[ \n]+\n/g,"\n").replace(/\n\n+/g,"\n\n").replace(/^\n+|\n+$/g,""),f.match("\n")||(f=f.replace(/^ +| +$/,"")),a(f),document.body.removeChild(c)})}();

Simply copy and paste it to the "URL" portion of your bookmark as shown below:

bookmarklet

3
  • Why do you return a new promise? I am getting uncaught error in promise. I think cause res and rej don't exist? Commented Sep 6, 2021 at 5:29
  • Are you using the minified version? I have added an alternative minified version in the edited section of my response for you to try. It might work better for you depending on what you are trying to do. Commented Sep 7, 2021 at 1:05
  • I will try. Thanks! Commented Sep 12, 2021 at 4:44

Not the answer you're looking for? Browse other questions tagged or ask your own question.