{"id":3629,"date":"2024-08-23T11:57:43","date_gmt":"2024-08-23T16:57:43","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=3629"},"modified":"2024-08-23T11:57:44","modified_gmt":"2024-08-23T16:57:44","slug":"writing-to-the-clipboard-in-javascript","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/writing-to-the-clipboard-in-javascript\/","title":{"rendered":"Writing to the Clipboard in JavaScript"},"content":{"rendered":"\n<p>In my&nbsp;<a href=\"https:\/\/frontendmasters.com\/blog\/reading-from-the-clipboard-in-javascript\/\">last article<\/a>, I showed you how to enable your website to read a visitor&#8217;s clipboard. Now I&#8217;m going to follow up that guide with a look at&nbsp;<em>writing<\/em>&nbsp;to the clipboard. It goes without saying that in any use of this type of functionality, you should proceed with care and, most of all, respect for your visitors. I&#8217;ll talk a bit about what that means later in the article, but for now, let&#8217;s look at the API.<\/p>\n\n\n<div class=\"box article-series\">\n  <header>\n    <h3 class=\"article-series-header\">Article Series<\/h3>\n  <\/header>\n  <div class=\"box-content\">\n            <ol>\n                      <li>\n              <a href=\"https:\/\/frontendmasters.com\/blog\/reading-from-the-clipboard-in-javascript\/\">Reading from the Clipboard in JavaScript<\/a>\n            <\/li>\n                      <li>\n              <a href=\"https:\/\/frontendmasters.com\/blog\/writing-to-the-clipboard-in-javascript\/\">Writing to the Clipboard in JavaScript<\/a>\n            <\/li>\n                      <li>\n              <a href=\"https:\/\/frontendmasters.com\/blog\/handling-paste-events-in-javascript\/\">Handling Paste Events in JavaScript<\/a>\n            <\/li>\n                  <\/ol>\n        <\/div>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"before-we-begin\">Before we begin&#8230;<\/h2>\n\n\n\n<p>As I said last time, clipboard functionality on the web requires a &#8220;secure context&#8221;. So if you&#8217;re running an http site (as opposed to an https site), these features will not work. I&#8217;d highly encourage you to get your site on https. That being said, these features, and others like them that require secure contexts, will still work on&nbsp;<code>http:\/\/localhost<\/code>. There&#8217;s no need to set up a temporary certificate when doing local testing.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"the-clipboard-api\">The Clipboard API<\/h2>\n\n\n\n<p>I covered this last time, but in case you didn&#8217;t read the previous article in this series, the&nbsp;<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Clipboard\">Clipboard API<\/a>&nbsp;is supported in JavaScript via&nbsp;<code>navigator.clipboard<\/code>&nbsp;and has excellent cross-platform support:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"788\" height=\"975\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/08\/write-clipboard-browser-support.webp?resize=788%2C975&#038;ssl=1\" alt=\"\" class=\"wp-image-3630\" style=\"width:469px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/08\/write-clipboard-browser-support.webp?w=788&amp;ssl=1 788w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/08\/write-clipboard-browser-support.webp?resize=242%2C300&amp;ssl=1 242w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/08\/write-clipboard-browser-support.webp?resize=768%2C950&amp;ssl=1 768w\" sizes=\"auto, (max-width: 788px) 100vw, 788px\" \/><\/figure>\n<\/div>\n\n\n<p>This feature will also prompt the user for permission so remember to handle cases where they reject the request.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"writing-to-the-clipboard-1\">Writing to the Clipboard<\/h2>\n\n\n\n<p>When I last discussed the clipboard API, I mentioned how it had two APIs for reading from the clipboard, we had a&nbsp;<code>readText<\/code>&nbsp;method tailored for, you guessed it, reading text, and a more generic&nbsp;<code>read<\/code>&nbsp;method for handling complex data. Unsurprisingly, we&#8217;ve got the same on the write side:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>write<\/code><\/li>\n\n\n\n<li><code>writeText<\/code><\/li>\n<\/ul>\n\n\n\n<p>And just like before,&nbsp;<code>writeText<\/code>&nbsp;is specifically for writing text to the clipboard while&nbsp;<code>write<\/code>&nbsp;gives you additional flexibility.<\/p>\n\n\n\n<p>At the simplest, you can use it like so:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">await<\/span> navigator.clipboard.writeText(<span class=\"hljs-string\">\"Hello World!\"<\/span>);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>That&#8217;s literally it. Here&#8217;s a Pen demonstrating an example of this, but you will most likely need to make use of <a href=\"https:\/\/codepen.io\/pen\/debug\/xxojZOL\">the &#8216;debug&#8217; link<\/a> to see this in action due to the permissions complication of cross-domain iframes:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_xxojZOL\" src=\"\/\/codepen.io\/anon\/embed\/xxojZOL?height=450&amp;theme-id=47434&amp;slug-hash=xxojZOL&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed xxojZOL\" title=\"CodePen Embed xxojZOL\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>One pretty simple and actually practical use-case for something like this is quickly copying links to the user&#8217;s clipboard. Let&#8217;s consider some simple HTML:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"example\"<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"https:\/\/www.raymondcamden.com\"<\/span>&gt;<\/span>My blog<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">br<\/span> \/&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"https:\/\/frontendmasters.com\/blog\/\"<\/span>&gt;<\/span>Frontend Masters blog<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>What I want to do is:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Pick up all the links (filtered by something logical)<\/li>\n\n\n\n<li>Automatically add a UI item that will copy the URL to the clipboard<\/li>\n<\/ol>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">links = <span class=\"hljs-built_in\">document<\/span>.querySelectorAll(<span class=\"hljs-string\">\"div.example p:first-child a\"<\/span>);\n\nlinks.forEach(<span class=\"hljs-function\">(<span class=\"hljs-params\">a<\/span>) =&gt;<\/span> {\n  <span class=\"hljs-keyword\">let<\/span> copy = <span class=\"hljs-built_in\">document<\/span>.createElement(<span class=\"hljs-string\">\"span\"<\/span>);\n  copy.innerText = <span class=\"hljs-string\">\"&#91;Copy]\"<\/span>;\n  copy.addEventListener(<span class=\"hljs-string\">\"click\"<\/span>, <span class=\"hljs-keyword\">async<\/span> () =&gt; {\n    <span class=\"hljs-keyword\">await<\/span> navigator.clipboard.writeText(a.href);\n  });\n  a.after(copy);\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>I begin with a selector for the links I care about, and for each, I append a <code>&lt;button&gt;<\/code> element with&nbsp;<code>\"Copy URL of Link\"<\/code>&nbsp;as the text. Each new element has a click handler to support copying its related URL to the clipboard. As before, here&#8217;s the CodePen but expect to need <a href=\"https:\/\/codepen.io\/pen\/debug\/bGPMEvN\">the debug link<\/a>:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_bGPMEvN\" src=\"\/\/codepen.io\/anon\/embed\/bGPMEvN?height=450&amp;theme-id=47434&amp;slug-hash=bGPMEvN&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed bGPMEvN\" title=\"CodePen Embed bGPMEvN\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>As a quick note, the UX of this demo could be improved, for example, notifying the user in some small way that the text was successfully copied. <\/p>\n\n\n\n<p>Now let&#8217;s kick it up a notch and look into how to support binary data with the&nbsp;<code>write<\/code>&nbsp;method.<\/p>\n\n\n\n<p>The basic interface for&nbsp;<code>write<\/code>&nbsp;is to pass an array of&nbsp;<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/ClipboardItem\"><code>ClipboardItem<\/code><\/a>&nbsp;objects. The MDN docs for&nbsp;<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Clipboard\/write\">write<\/a>&nbsp;provide this example:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> type = <span class=\"hljs-string\">\"text\/plain\"<\/span>;\n<span class=\"hljs-keyword\">const<\/span> blob = <span class=\"hljs-keyword\">new<\/span> Blob(&#91;text], { type });\n<span class=\"hljs-keyword\">const<\/span> data = &#91;<span class=\"hljs-keyword\">new<\/span> ClipboardItem({ &#91;type]: blob })];\n<span class=\"hljs-keyword\">await<\/span> navigator.clipboard.write(data);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>That seems sensible. For my first attempt, I decided to add simple &#8220;click to copy&#8221; support to an image. So consider this HTML:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"house.jpg\"<\/span> <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">\"a house\"<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>I could support this like so:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-built_in\">document<\/span>.addEventListener(<span class=\"hljs-string\">'DOMContentLoaded'<\/span>, init, <span class=\"hljs-literal\">false<\/span>);\n\n<span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">init<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-built_in\">document<\/span>.querySelector(<span class=\"hljs-string\">'img'<\/span>).addEventListener(<span class=\"hljs-string\">'click'<\/span>, copyImagetoCB);\n}\n\n<span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">copyImagetoCB<\/span>(<span class=\"hljs-params\">e<\/span>) <\/span>{\n  <span class=\"hljs-comment\">\/\/ should be dynamic<\/span>\n  <span class=\"hljs-keyword\">let<\/span> type = <span class=\"hljs-string\">'image\/jpeg'<\/span>;\n  <span class=\"hljs-keyword\">let<\/span> dataReq = <span class=\"hljs-keyword\">await<\/span> fetch(e.target.src);\n  <span class=\"hljs-keyword\">let<\/span> data = <span class=\"hljs-keyword\">await<\/span> dataReq.blob();\n\n  <span class=\"hljs-keyword\">let<\/span> blob = <span class=\"hljs-keyword\">new<\/span> Blob(&#91;data], { type });\n\n  <span class=\"hljs-keyword\">let<\/span> cbData = &#91;<span class=\"hljs-keyword\">new<\/span> ClipboardItem({ &#91;type]: blob })];\n  <span class=\"hljs-keyword\">await<\/span> navigator.clipboard.write(cbData);\n\n  <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">'Done'<\/span>);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This code picks up the one and only image, adds a click handler, and makes use of the MDN sample code, updated to use a hard-coded type (JPG) and to fetch the binary data. (This kind of bugs me. I know the image is loaded in the DOM so it feels like I should be able to get access to the bits without another network call, but from what I can see, you can only do this by using a Canvas object.)<\/p>\n\n\n\n<p>But when run, you get something interesting:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">Uncaught (in promise) DOMException: Failed to execute 'write' on 'Clipboard': Type image\/jpeg not supported on write.<\/pre>\n\n\n\n<p>What??? Turns out, this is actually documented on MDN:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Browsers commonly support writing text, HTML, and PNG image data<\/p>\n<\/blockquote>\n\n\n\n<p>This seems&#8230; crazy. I mean I definitely get having some restrictions, but it feels odd for only one type of image to be supported. However, changing my HTML:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"house.png\"<\/span> <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">\"a house\"<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>And the type in my JavaScript:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">let<\/span> type = <span class=\"hljs-string\">'image\/png'<\/span>;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Confirms that it works. You can see this yourself below (and again, <a href=\"https:\/\/codepen.io\/pen\/debug\/XWLqXqe\">hit that &#8216;Debug&#8217; link<\/a>):<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_XWLqXqe\" src=\"\/\/codepen.io\/anon\/embed\/XWLqXqe?height=450&amp;theme-id=47434&amp;slug-hash=XWLqXqe&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed XWLqXqe\" title=\"CodePen Embed XWLqXqe\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>So remember above when I said I didn&#8217;t want to use a canvas? I did some Googling and turns out: I need to use a canvas. I found an excellent example of this on StackOverflow in this&nbsp;<a href=\"https:\/\/stackoverflow.com\/a\/62911176\/52160\">answer<\/a>. In it, the author uses a temporary canvas, writes the image data to it, and uses&nbsp;<code>toBlob<\/code>&nbsp;while specifying a PNG image type. Whew. So let&#8217;s see if we can build a generic solution.<\/p>\n\n\n\n<p>First, I updated my HTML to support two images:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"photos\"<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n    JPG:<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">br<\/span>\/&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"house.jpg\"<\/span> <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">\"a house\"<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n    PNG:<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">br<\/span>\/&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"house.png\"<\/span> <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">\"a house\"<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>I labeled them as I was using the same picture each and needed a simple way to know which was which.<\/p>\n\n\n\n<p>Next, I updated my code to pick up&nbsp;<em>all<\/em>&nbsp;images in that particular div:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-built_in\">document<\/span>.addEventListener(<span class=\"hljs-string\">'DOMContentLoaded'<\/span>, init, <span class=\"hljs-literal\">false<\/span>);\n\n<span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">init<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">let<\/span> imgs = <span class=\"hljs-built_in\">document<\/span>.querySelectorAll(<span class=\"hljs-string\">'div.photos img'<\/span>);\n  imgs.forEach(<span class=\"hljs-function\"><span class=\"hljs-params\">i<\/span> =&gt;<\/span> {\n    i.addEventListener(<span class=\"hljs-string\">'click'<\/span>, copyImagetoCB);\n  });\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Now for the fun part. I&#8217;m going to update my code to detect the type of the image. For PNG&#8217;s, it will use what I showed before, and for JPGs, I&#8217;m using a modified version of the StackOverflow answer. Here&#8217;s the updated code:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">copyImagetoCB<\/span>(<span class=\"hljs-params\">e<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">let<\/span> type = e.target.src.split(<span class=\"hljs-string\">\".\"<\/span>).pop();\n  <span class=\"hljs-keyword\">let<\/span> blob;\n  <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"type\"<\/span>, type);\n\n  <span class=\"hljs-comment\">\/\/ support JPG\/PNG only<\/span>\n  <span class=\"hljs-keyword\">if<\/span> (type === <span class=\"hljs-string\">\"jpg\"<\/span> || type === <span class=\"hljs-string\">\"jpeg\"<\/span>) {\n    blob = <span class=\"hljs-keyword\">await<\/span> setCanvasImage(e.target.src);\n  } <span class=\"hljs-keyword\">else<\/span> <span class=\"hljs-keyword\">if<\/span> (type.endsWith(<span class=\"hljs-string\">\"png\"<\/span>)) {\n    <span class=\"hljs-keyword\">let<\/span> dataReq = <span class=\"hljs-keyword\">await<\/span> fetch(e.target.src);\n    <span class=\"hljs-keyword\">let<\/span> data = <span class=\"hljs-keyword\">await<\/span> dataReq.blob();\n\n    blob = <span class=\"hljs-keyword\">new<\/span> Blob(&#91;data], { <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-string\">\"image\/png\"<\/span> });\n  }\n\n  <span class=\"hljs-keyword\">let<\/span> cbData = &#91;<span class=\"hljs-keyword\">new<\/span> ClipboardItem({ <span class=\"hljs-string\">\"image\/png\"<\/span>: blob })];\n  <span class=\"hljs-keyword\">await<\/span> navigator.clipboard.write(cbData);\n  <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"Done\"<\/span>);\n}\n\n<span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">setCanvasImage<\/span>(<span class=\"hljs-params\">path<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Promise<\/span>(<span class=\"hljs-function\">(<span class=\"hljs-params\">resolve, reject<\/span>) =&gt;<\/span> {\n    <span class=\"hljs-keyword\">const<\/span> img = <span class=\"hljs-keyword\">new<\/span> Image();\n    <span class=\"hljs-keyword\">const<\/span> c = <span class=\"hljs-built_in\">document<\/span>.createElement(<span class=\"hljs-string\">\"canvas\"<\/span>);\n    <span class=\"hljs-keyword\">const<\/span> ctx = c.getContext(<span class=\"hljs-string\">\"2d\"<\/span>);\n\n    img.onload = <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\"><\/span>) <\/span>{\n      c.width = <span class=\"hljs-keyword\">this<\/span>.naturalWidth;\n      c.height = <span class=\"hljs-keyword\">this<\/span>.naturalHeight;\n      ctx.drawImage(<span class=\"hljs-keyword\">this<\/span>, <span class=\"hljs-number\">0<\/span>, <span class=\"hljs-number\">0<\/span>);\n      c.toBlob(<span class=\"hljs-function\">(<span class=\"hljs-params\">blob<\/span>) =&gt;<\/span> {\n        resolve(blob);\n      }, <span class=\"hljs-string\">\"image\/png\"<\/span>);\n    };\n    img.src = path;\n  });<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>You&#8217;ll note that the main difference is how we get the blob. For JPGs, we&#8217;re using&nbsp;<code>setCanvasImage<\/code>&nbsp;to handle the canvas shenanigans (that&#8217;s what I&#8217;m calling it) and return a PNG blob.<\/p>\n\n\n\n<p>You can see this in the CodePen below, if, again, you click out to the debug view. Note that I had to add one additional line:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">img.crossOrigin = <span class=\"hljs-string\">\"anonymous\"<\/span>;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>As without it, you get a tainted canvas error. Sounds dirty.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_dyBeGeg\" src=\"\/\/codepen.io\/anon\/embed\/dyBeGeg?height=850&amp;theme-id=47434&amp;slug-hash=dyBeGeg&amp;default-tab=result\" height=\"850\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed dyBeGeg\" title=\"CodePen Embed dyBeGeg\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"with-great-power\">With great power&#8230;<\/h2>\n\n\n\n<p>It goes without saying that this API could be abused, or, more likely, used in kind of a jerky manner. So for example, if you give the user the ability to click to copy a URL, don&#8217;t do sneaky crap like:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">\"(url here) - This URL comes from the awesome site X and you should visit it for deals!\"<\/pre>\n\n\n\n<p>If you are expressing to the user that you are copying something in particular, adding to it or modifying it is simply rude. I&#8217;d even suggest that copying a short URL link instead of the original would be bad form as well.<\/p>\n\n\n<div class=\"box article-series\">\n  <header>\n    <h3 class=\"article-series-header\">Article Series<\/h3>\n  <\/header>\n  <div class=\"box-content\">\n            <ol>\n                      <li>\n              <a href=\"https:\/\/frontendmasters.com\/blog\/reading-from-the-clipboard-in-javascript\/\">Reading from the Clipboard in JavaScript<\/a>\n            <\/li>\n                      <li>\n              <a href=\"https:\/\/frontendmasters.com\/blog\/writing-to-the-clipboard-in-javascript\/\">Writing to the Clipboard in JavaScript<\/a>\n            <\/li>\n                      <li>\n              <a href=\"https:\/\/frontendmasters.com\/blog\/handling-paste-events-in-javascript\/\">Handling Paste Events in JavaScript<\/a>\n            <\/li>\n                  <\/ol>\n        <\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>The most basic use case, writing a bit of text to the user&#8217;s clipboard, is mercifully easy. But there is plenty more to know. Did you know writing image data to the clipboard ONLY works with PNG?<\/p>\n","protected":false},"author":29,"featured_media":3189,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[217,3],"class_list":["post-3629","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-clipboard","tag-javascript"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/pexels-photo-544965.jpeg?fit=1880%2C1253&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/3629","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/users\/29"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=3629"}],"version-history":[{"count":11,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/3629\/revisions"}],"predecessor-version":[{"id":3664,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/3629\/revisions\/3664"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/3189"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=3629"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=3629"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=3629"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}