A clipboard magic trick - how to use different MIME types with the Clipboard API
- Published at
- Updated at
- Reading time
- 6min
I discovered a web development magic trick from Cyrus Roshan today. It took me a good 20 minutes to figure out how the trick works, and I learned some things about JavaScript clipboard handling and MIME types. Sounds intriguing? Read on!
Try the magic tricks yourself; I'll wait. ๐
If you didn't try it, here's the flow:
- You're tasked to click an ASCII art play card. The card's characters are automatically copied to your clipboard using JavaScript.
- Then, a new screen tells you to paste the copied ASCII card into a textarea (which works as expected).
- Next, you're told to open a new Google Doc and paste your clipboard content into it (the ASCII art playing card).
- The just copied ASCII art now includes a new line telling you to paste the same content into the URL bar, and boom! ๐ช You just pasted Cyrus' Twitter profile URL.
Whoot? That's magic! ๐คฏ
After creating fifteen different Google documents wondering if Cyros somehow injects JavaScript into Google Docs (which he doesn't), I figured out how this trick works.
Cyros' page leverages a nifty feature of the JavaScript Clipboard API (navigator
), and like every magic trick, once you know how it works, it's stupidly simple.
If you've been doing web development long enough, you might remember the document
command. This old way to interact with the clipboard is now deprecated and replaced by the Clipboard API. The newer API has the single purpose of interacting with the clipboard and works asynchronously. Yay!
But does the Clipboard API work everywhere today? At first look, navigator
seems to be cross-browser supported...
... but watch out! Looking deeper into it, you'll find out that just because navigator
is available, it doesn't mean all functionality is available.
How to place plain text in the clipboard
Putting text into the clipboard is straightforward using the API. Here's an example.
await navigator.clipboard.writeText(
"That's some cool copied text, isn't it?"
);
Click the button below and paste the new clipboard content into the input fields to confirm it works.
writeText
covers many standard use cases, but it's not what the magic trick uses. Let's dig deeper!
How to write different MIME types to the clipboard
As seen, placing text on the clipboard is quickly done. But how would you handle images or other text formats such as richtext or HTML? Is it possible to put these into the clipboard with JavaScript, too?
There's another method available to put content in the clipboard โ clipboard
.
await navigator.clipboard.write([
new ClipboardItem({
'text/plain': new Blob(["That's some cool plain text, isn't it?"], {
type: 'text/plain',
}),
}),
]);
clipboard
doesn't accept strings but ClipboardItems
. The main difference between the two methods is that if you want to put anything but plain text into the clipboard, you must define the matching MIME type using a ClipboardItem
.
It's more code to write but still a decent experience in my opinion. Sweet!
76 | 84 | 79 | 127 | 127 | 13.1 | 13.1 | 12.0 | 76 |
And here's annother example to play with. It looks the same as the previous one, but now it uses navigator
.
Fill up your clipboard!
Can you already imagine how the magic trick works now that you've seen some code?
That's right; the trick is based on different content MIME types. Input fields and textareas handle pasted plain text just fine, but there are obviously other available MIME types.
A clipboard could hold types of image/gif
, image/jpeg
, text/rtf
, the good old text/html
, and all sorts of fanciness.
And thanks to the Clipboard API, you're in control of the MIME type and can even store text and images in the same write operation.
And it's not only a single operation; it's even a single clipboard entry.
navigator.clipboard.write([
new ClipboardItem({
'text/plain': new Blob(["That's some cool plain text, isn't it?"], {
type: 'text/plain',
}),
'text/html': new Blob(
[
'<div style="/* some styles */">Oh yeah - text/html!</div>',
],
{
type: 'text/html',
}
),
}),
]);
The example above shows how to put different content as plain text and HTML into your clipboard. ๐ฒ
Now it's only a matter of where you paste the content to see this magic in action.
A div
with an contentEditable
attribute can accept and render HTML. ๐ฒ If you paste content with the MIME type text/html
into it, it will render it just fine.
To prove it, hit the button below and see what happens when you paste it into the input fields and the editable div
.
div
with contentEditable
Cyrus' trick uses this functionality.
Initially, the magic trick puts plain text into the clipboard, but later on, it stores a ClipboardItem
with multiple MIME types. text/plain
holds his Twitter profile URL and text/html
includes the ASCII art card. Google Docs then renders the pasted HTML, whereas the URL bar renders the plain text.
If you're using MIME types other than text, it's good to provide a text/plain
fallback if your target doesn't understand a particular MIME type.
How to inspect your clipboard
While I was debugging the magic trick, I discovered that inspecting your clipboard isn't straightforward on MacOS. Even though the Finder provides a way to look at what's in the clipboard (Finder > Edit > Show clipboard
), it always shows the plain text entry.
I built a quick clipboard inspector using the Clipboard API's read
methods. And here it became very interesting.
Unfortunately, it's the same story of Firefox not supporting complex clipboard interactions (it's behind another flag โ dom
) and even though Safari supports navigator
it has a surprise for us.
76 | 76 | 79 | 127 | 127 | 13.1 | 13.1 | 12.0 | 76 |
MDN explains to use navigator
as follows:
try {
const permission = await navigator.permissions.query({ name: 'clipboard-read' });
if (permission.state === 'denied') {
throw new Error('Not allowed to read clipboard.');
}
const clipboardContents = await navigator.clipboard.read();
for (const item of clipboardContents) {
// do things with the clipboard entries
}
} catch (error) {
console.error(error.message);
}
It works fine in Chromiums, but it turns out that Safari doesn't support navigator
. ๐คฆโโ๏ธ
43 | 43 | 79 | 46 | 46 | 16 | 16 | 4.0 | Nei |
This means you have to check if navigator
is available, too. And if it is, ask for permissions and if not, try to use navigator
anyways.
In this case, Safari shows a little "Paste" mini permission dialog. If you don't click it, navigator
will throw an exception. Ufff...
Here's a summary on how to use navigator
:
- For Chromiums you should use the Permissions API.
- You can't read the clipboard content using Firefox.
- In Safari you just have to try it and see if it works.
Have fun with it below.
Side note: not all clipboard content is accessible
Inspecting and accessing text-based clipboard content seemed to work fine in Chromiums. But if I copy an image from the MacOS Finder navigator
doesn't like that either and throws a No valid data on clipboard
exception.
So, if you're planning to use navigator
, you have to feature detect the Permissions API and also make sure to try/catch
all your read
calls.
This little magic trick became quite a journey. But here's what I learned:
- The Clipboard API allows you to write multiple entries in different MIME types to the clipboard.
- Using the Clipboard API is still a pain if you're targeting all major browsers.
- Not everything in your clipboard is accessible via JavaScript.
If you want to learn more there's a good article on the async Clipboard API on web
and Thomas has you covered, too.
And with this, happy pasting! ๐
Join 5.3k readers and learn something new every week with Web Weekly.