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
}
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);
});
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);
}
}
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);
});
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);
}
}
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);
}
});
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);
}
});
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);
};
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';
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);
}
}
}
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)
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.
Good read, slight typo "On of the reasons the catch function..."
ππΌ
Great post! I think there is a typo for the copy event you seem to be using paste again for event name
Oooh good catch, thanks