This tutorial post will show you a simple and reliable way of getting access to copy, cut and paste keyboard shortcuts in your browser webgame in JavaScript, all without having to use fiddly APIs full of caveats like document.execCommand. Check out the result or follow along below.

To start create a basic blank HTML file. First we’ll add a textarea element and position it offscreen:

<textarea id="text" style="position: fixed; left: -500px; top: 0"></textarea>

This textarea will let us get and set copy from and to the clipboard. We’ve positioned it to the left of the screen so it should be invisible to the user. Next we’ll add something that lets us show some output (you could also just use console.log):

<p><b>Output</b>
<pre id="output"></pre></p>

Now let’s get into the code! The theory of this approach is actually not that complicated, which is nice. It doesn’t work on non-desktop devices, but oh well. Mobile input is a pain in the ass anyway. Basically what we want to do is this:

  • Know when the user presses Control+C, Control+X or Control+Z
  • Focus the textarea element, putting text to copy into it if copying
  • Have the browsers innate operations perform on the textarea; copying text we put in there to the clipboard, or pasting text the user wants to paste in
  • Restore focus to whatever was focused before

There’s a few things in JavaScript we can leverage to do this:

  • keydown event: fires first, lets us know when the keyboard shortcuts are used
  • copy, cut and paste events: come after, letting us know the operation has finished
  • setTimeout: used to trigger an event after the clipboard events

We’ll add a script tag and put the following code in it to catch when the user uses the shortcuts:

var prevFocus;

document.body.addEventListener("keydown", function(e) {
  // e.which === 67 is 'c', e.which === 88 is 'x'
  if ((e.which === 67 || e.which === 88) && e.ctrlKey) {
    prevFocus = document.activeElement;
    var ele = document.getElementById("text");
    ele.focus();
    ele.value = getCopyText(e.which === 88);
    ele.select();
  }
  // e.which === 86 is 'v'
  if (e.which === 86 && e.ctrlKey) {
    prevFocus = document.activeElement;
    var ele = document.getElementById("text");
    ele.focus();
    ele.select();
  }
});

When the keydown event fires the above code gets called. If the user pressed the copy or cut shortcut, the code stores the active document, gets the textarea, focuses it, and calls a function that gets the text we want the user to copy, indicating whether this is a cut operation with a boolean argument, and finally selects the new text. If the user pressed the paste shortcut, the code stores the active document, gets the textarea, focuses it, and selects all text so it’ll be replaced by the paste operation.

The getCopyText function would be something defined by your game, presumably some GUI text widget with selectable text. You’ll notice that we’re attaching the keydown event to the body element. You don’t have to do that, but if you do you want your game to use some checking in this function to see if it has the user’s focus rather than some other element on the page. If your GUI widget allows for text modification, you’ll want to delete in this function for cut operations. For our purposes we’ll just have it return some random text and ignore the cut argument:

function getCopyText(cut) {
  return "Hello world!";
}

Now if the user uses the clipboard shortcuts, the textarea will be focused to do what we need it to, send our custom text or receive the pasted text. We’re already halfway there! Now we just need to know when the operation has completed. So lets do that:

var copyHandler = function(e) {
  var ele = document.getElementById("text");
  var text = ele.value;
  
  setTimeout(function() {
    onCopy(text);
    
    ele.blur();
    if (prevFocus) {
      prevFocus.focus();
      prevFocus = null;
    }
    else {
      document.body.focus();
    }
  }, 0);
};

document.body.addEventListener("copy", copyHandler);
document.body.addEventListener("cut", copyHandler);

document.body.addEventListener("paste", function(e) {
  setTimeout(function() {
    var ele = document.getElementById("text");
    onPaste(ele.value);
    
    ele.blur();
    if (prevFocus) {
      prevFocus.focus();
      prevFocus = null;
    }
    else {
      document.body.focus();
    }
  }, 0);
});

We create one function for both copy and cut events, since we already performed the cut operation in the getCopyText function, and attach it to the right events. We also create a very similar function for the paste event. You’ll notice that both functions use setTimeout with zero milliseconds and don’t actually do anything themselves; that’s because the clipboard events fire before the actual operation completes. Using setTimeout in this way is like pushing an event into the queue to fire right after the clipboard events, so we know they’ve completed.

For the rest of it, they either call an onCopy or onPaste function (defined by your game), then remove focus from the textarea and restore it to whatever had focus before, or the document body otherwise. And that’s all we need. We’ll implement onCopy and onPaste for testing purposes:

function onCopy(text) {
  document.getElementById("output").innerHTML = "Copied:\n" + text;
}

function onPaste(text) {
  document.getElementById("output").innerHTML = "Pasted:\n" + text;
}

And that’s it! A very simple method to get reliable clipboard operations for your JavaScript webgame using keyboard shortcuts and without messing with the execCommand or clipboardEvent API. If you liked this post maybe give it a share using one of the buttons below or check out options to support our work.