DEV Community

Yaser Adel Mehraban
Yaser Adel Mehraban

Posted on • Edited on • Originally published at yashints.dev

Get to know the Clipboard API, be smarter with user interactions πŸ“‹

We face many situations in which we need to interact with user's clipboard. Up until recently, browsers were using document.execCommand for clipboard interactions. It sounded great and it was (and still is) widely supported way to copy, cut, and paste into web apps, but the catch was that clipboard access is asynchronous and can just write to DOM.

Background

The fact that clipboard access is asynchronous means the user experience was suffering because a clipboard transfer was happening. This got worst when pasting because in most cases you need to sanitise the content can be safely processed.

It even got worse when permissions got into the equation, or the browser had to copy information from a link in the text and page had to wait for a network request, so the usage wasn't that widespread.

Clipboard API

It was all sad and gloomy until the new Clipboard API was born. This API is a replacement for document.execCommand based copy and pasting that has a well defined permissions model and doesn't block the page.

This API is a property of the window.navigator and is constructed from below properties and methods:

  • read()
  • readText()
  • write(data)
  • writeText(text)
  • addEventListener(event, handler)
  • removeEventListener(event, handler)
  • dispatchEvent(event)

To detect whether the feature is supported in the browser or not you can do:

if (navigator.clipboard) {
  // yep, happy coding.
} else {
  // oh no 😒. Use execCommand for now
}
Enter fullscreen mode Exit fullscreen mode

In addition to that goodness, it can also read from and write to system clipboard 😍. Access to clipboard is behind Permissions API, which means without user's permission none of these operations are permitted.

Copy text to clipboard

Text can be copied to the clipboard by calling the writeText method. This function returns a promise that will be resolved or rejected based on how the operation was performed.

navigator.clipboard.writeText('I am writing to clipboard πŸ“‹')
  .then(() => {
    console.log('Woohoo');
  })
  .catch(err => {    
    console.error('Oh no: ', err);
  });
Enter fullscreen mode Exit fullscreen mode

One of the reasons the catch function might be called is that the permission is not granted by the user or they deny it.

You can use an async function and await:

async function writeTextToClipboard() {
  try {
    await navigator.clipboard.writeText('I am writing to clipboard πŸ“‹');
    console.log('Woohoo');  
  } catch (err) {
    console.error('Oh no: ', err);
  }
}
Enter fullscreen mode Exit fullscreen mode

Read from clipboard

To read the copied text from clipboard, you can use the readText method and wait for the returned promise:

navigator.clipboard.readText()
  .then(text => {
    console.log('Pasted content: ', text);
  })
  .catch(err => {
    console.error('Oh no: ', err);
  });
Enter fullscreen mode Exit fullscreen mode

And with async/await:

async function getCopiedTextFromClioboard() {
  try {
    const text = await navigator.clipboard.readText();
    console.log('Pasted content: ', text);
  } catch (err) {
    console.error('Oh no: ', err);
  }
}
Enter fullscreen mode Exit fullscreen mode

Let's see this in a demo.

Notice you had to give permission for it to work.

Handling paste

You can hook up to the (surprise) paste event on the document to be able to receive the text when it's copied into clipboard and a paste action is triggered by user agent. If you want to modify the received data, you need to prevent the event from bubbling up.

document.addEventListener('paste', async (e) => {
  e.preventDefault(); // need to do something with the text
  try {
    let text = await navigator.clipboard.readText();
    text = text.toUpperCase();
    console.log('Pasted UPPERCASE text: ', text);
  } catch (err) {
    console.error('Oh no: ', err);
  }
});
Enter fullscreen mode Exit fullscreen mode

Handling copy

Similar to paste, you can handle copy events too:

document.addEventListener('copy', async (e) => {
  e.preventDefault(); 
  try {
    for (const item of e.clipboardData.items) {
      await navigator.clipboard.write([
        new ClipboardItem({
          [blob.type]: blob
        })
      ]);
    }
    console.log('Image copied.');
  } catch (err) {
    console.error('Oh no: ', err);
  }
});
Enter fullscreen mode Exit fullscreen mode

Permissions

The navigator.clipboard is only supported for pages served over HTTPS, plus clipboard access is restricted only when allowed to prevent further abuse. Active pages can write to clipboard, but reading requires granted permission.

There two new permissions which was introduced when Clipboard API was developed:

  • The clipboard-write is granted automatically to any active tab.
  • The clipboard-read must be requested.
const queryOpts = { name: 'clipboard-read' };
const permissionStatus = await navigator.permissions.query(queryOpts);
// Will be 'granted', 'denied' or 'prompt':
console.log(permissionStatus.state);

// Listen for changes to the permission state
permissionStatus.onchange = () => {
  console.log(permissionStatus.state);
};
Enter fullscreen mode Exit fullscreen mode

Images

We don't always use text, so the new write() method was introduced to allow us to write other data formats to the clipboard. This method is asynchronous too and truth is, writeText is calling this method behind the scenes.

Write to clipboard

To write an image to clipboard, you need to convert your image to a Blob. You can do it using a canvas element by calling the toBlob() function. At this point you can only pass one image at a time and multi image is under progress by Chrome team.

const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext('2d');

var img = new Image();
img.setAttribute('crossorigin', 'anonymous');
img.onload = function() {
  ctx.drawImage(img, 0, 0, 150, 150);
  img.style.display = 'none';

  canvas.toBlob(function(blob) {
    navigator.clipboard.write([
      new ClipboardItem({
        [blob.type]: blob
      })
    ]);
  })
};

img.src = 'https://image-url';
Enter fullscreen mode Exit fullscreen mode

Read the image from clipboard

To read the image you'll need to use the read() method, which reads the data and returns a list of ClipboardItem objects. You can use for...of which handles the async part for you as well.

Each returned object can have different type, so best option would be to call getType() on it and act accordingly. In our case the corresponding type would be Blob.

const items = await navigator.clipboard.read(); 

for (const clipboardItem of items) {
  for (const type of clipboardItem.types) {        
    if (type === "image/png") {
      console.log(type);
      const blob = await clipboardItem.getType(type);        
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

You can see a full demo here:

Browser support

The browser support for this API as of writing this article is not there yet. Chrome and Firefox are fully supporting both the Clipboard API and ClipboardEvent API.

Edge doesn't support it and it's been worked on in Safari.

Summary

We saw how easy and convenient it is to work with clipboard with this awesome API, and how much better the user experience will be.

So without further ado, happy coding πŸ’».

Top comments (5)

Collapse
 
ollie profile image
ollie • Edited

Unfortunately I don't think the support and stability is quite there yet, so not very useful in real production environments. I did for a minute get excited I could dump a dependency though.

Collapse
 
amcmillan01 profile image
Andrei McMillan

Good read, slight typo "On of the reasons the catch function..."

Collapse
 
yashints profile image
Yaser Adel Mehraban

πŸ™πŸΌ

Collapse
 
sedighian profile image
Sam Sedighian

Great post! I think there is a typo for the copy event you seem to be using paste again for event name

Collapse
 
yashints profile image
Yaser Adel Mehraban

Oooh good catch, thanks