React - how to copy an image to clipboard?
Asked Answered
A

5

5

im working on react web app and one of the feature needs to be implemented is to copy an image when clicked, so the user could paste it in: paint, word, etc...

i tried several approaches, first was to follow the instructions detailed in this post: https://mcmap.net/q/326485/-copy-image-to-clipboard

this is what i came up with (the containerId refers to a div element which contains an image element as its first child):

copyImg = (containerId) => {
const imgContainerElement = document.getElementById(containerId);
this.selectText(imgContainerElement.children[0]);
document.execCommand('copy');
window.getSelection().removeAllRanges();
alert('image copied!');
}

selectText = (element) => {
    var doc = document;
    if (doc.body.createTextRange) {
      var range = document.body.createTextRange();
      range.moveToElementText(element);
      range.select();
    } else if (window.getSelection) {
      var selection = window.getSelection();
      var range = document.createRange();
      range.selectNodeContents(element);
      selection.removeAllRanges();
      selection.addRange(range);
    }
  }

didn't work. I tried implement the solution marked with a 2 stars here: https://www.tek-tips.com/viewthread.cfm?qid=833917

  function copyImg(imgId){
  var r = document.body.createControlRange();
  r.add(document.getElementById(imgId));
  r.select();
  r.execCommand("COPY");
}

but the createControlRange() is undefined.

i tried using the navigator.clipboard api but it only works with png, and the app works with jpg.

i looked for an npm library that can accomplish this, but all i found was for text-copying. npms like: react-copy-to-clipboard

any help would be appreciated.

Edit 1:

following dw_https://mcmap.net/q/2000211/-react-how-to-copy-an-image-to-clipboard instructions this is what i came up with: (note: i had to npm install babel-polyfill and import it in App.js, in order to make the async function to work and pass this error: regeneratorRuntime is not defined)

    copyImg = async (imgElementId) => {
    const imgElement = document.getElementById(imgElementId);
    const src = imgElement.src;
    const img = await fetch(src);
    const imgBlob = await img.blob();
    if (src.endsWith(".jpg") || src.endsWith(".jpeg")) {
      copyService.convertToPng(imgBlob);
    } else if (src.endsWith(".png")) {
      copyService.copyToClipboard(imgBlob);
    } else {
      console.error("Format unsupported");
    }
 }

convertToPng = (imgBlob) => {
    const imageUrl = window.URL.createObjectURL(imgBlob);
    const canvas = document.createElement("canvas");
    const ctx = canvas.getContext("2d");
    const imageEl = createImage({ src: imageUrl });
    imageEl.onload = (e) => {
        canvas.width = e.target.width;
        canvas.height = e.target.height;
        ctx.drawImage(e.target, 0, 0, e.target.width, e.target.height);
        canvas.toBlob(copyToClipboard, "image/png", 1);
    };
}

createImage = (options) => {
    options = options || {};
    const img = (Image) ? new Image() : document.createElement("img");
    if (options.src) {
        img.src = options.src;
    }
    return img;
  }

copyToClipboard = (pngBlob) => {
    try {
        navigator.clipboard.write([
            new ClipboardItem({
                [pngBlob.type]: pngBlob
            })
        ]);
        console.log("Image copied");
    } catch (error) {
        console.error(error);
    }
}

the code reaches to the Image copied message, but still when paste it on word it does not shown. anther thing is that i get

console error: Uncaught (in promise) DOMException

Aideaidedecamp answered 4/12, 2019 at 18:40 Comment(5)
Change copyToClipboard to copyService.copyToClipboard, and createImage({ src: imageUrl }); to copyService.createImage({ src: imageUrl });Brittain
Which line triggers the Uncaught in promise error?Brittain
navigator.clipboard.writeAideaidedecamp
jsfiddle.net/48trLf0q Does this jsfiddle work for you?Brittain
the fiddle also does not work. the line: navigator.clipboard.write throws DOMException: Document is not focused. also the react example failed. i think its because the photos are from fiddler api.Aideaidedecamp
B
6

Based on @Zohaib Ijaz's answer and Convert JPG images to PNG using HTML5 URL and Canvas article.

If the image is a jpeg/jpg, it will firstly convert the image to png using HTML5 canvas.

function createImage(options) {
  options = options || {};
  const img = (Image) ? new Image() : document.createElement("img");
  if (options.src) {
  	img.src = options.src;
  }
  return img;
}
       
function convertToPng(imgBlob) {
  const imageUrl = window.URL.createObjectURL(imgBlob);
  const canvas = document.createElement("canvas");
  const ctx = canvas.getContext("2d");
  const imageEl = createImage({ src: imageUrl });
  imageEl.onload = (e) => {
    canvas.width = e.target.width;
    canvas.height = e.target.height;
    ctx.drawImage(e.target, 0, 0, e.target.width, e.target.height);
    canvas.toBlob(copyToClipboard, "image/png", 1);
  };      
}

async function copyImg(src) {
   const img = await fetch(src);
   const imgBlob = await img.blob();
   if (src.endsWith(".jpg") || src.endsWith(".jpeg")) {
     convertToPng(imgBlob);
   } else if (src.endsWith(".png")) {
     copyToClipboard(imgBlob);
   } else {
     console.error("Format unsupported");
   }
}

async function copyToClipboard(pngBlob) {
    try {
      await navigator.clipboard.write([
        new ClipboardItem({
            [pngBlob.type]: pngBlob
        })
      ]);
      console.log("Image copied");
    } catch (error) {
        console.error(error);
    }
}

function copyImageViaSelector(selector) {
	copyImg(document.querySelector(selector).src);
}
  <img id="image" width="100" src="https://i.imgur.com/Oq3ie1b.jpg">
  <button onclick="copyImageViaSelector('#image')">Copy image</button>

React:

import React, { useRef } from "react";

const createImage = (options) => {
  options = options || {};
  const img = document.createElement("img");
  if (options.src) {
    img.src = options.src;
  }
  return img;
};

const copyToClipboard = async (pngBlob) => {
  try {
    await navigator.clipboard.write([
      // eslint-disable-next-line no-undef
      new ClipboardItem({
        [pngBlob.type]: pngBlob
      })
    ]);
    console.log("Image copied");
  } catch (error) {
    console.error(error);
  }
};

const convertToPng = (imgBlob) => {
  const canvas = document.createElement("canvas");
  const ctx = canvas.getContext("2d");
  const imageEl = createImage({ src: window.URL.createObjectURL(imgBlob) });
  imageEl.onload = (e) => {
    canvas.width = e.target.width;
    canvas.height = e.target.height;
    ctx.drawImage(e.target, 0, 0, e.target.width, e.target.height);
    canvas.toBlob(copyToClipboard, "image/png", 1);
  };
};

const copyImg = async (src) => {
  const img = await fetch(src);
  const imgBlob = await img.blob();
  const extension = src.split(".").pop();
  const supportedToBeConverted = ["jpeg", "jpg", "gif"];
  if (supportedToBeConverted.indexOf(extension.toLowerCase())) {
    return convertToPng(imgBlob);
  } else if (extension.toLowerCase() === "png") {
    return copyToClipboard(imgBlob);
  }
  console.error("Format unsupported");
  return;
};

const Image = () => {
  const ref = useRef(null);
  return (
    <div>
      <img id="image" ref={ref} width="100" src="https://i.imgur.com/Oq3ie1b.jpg" alt="" />
      <button onClick={() => copyImg(ref.current.src)}>copy img</button>
    </div>
  );
};

export default Image;

Known Limitations:

  • Does not work on IE / Safari / (Pre-chromium) Edge.
  • Only works on images that are on the same domain, or servers with relaxed CORS settings.
Brittain answered 4/12, 2019 at 19:50 Comment(4)
i edited the question. still it doesn't work. maybe it because of the same domain you mantioned. though it sounds strange: the image is already in the client browser, what CORS has to do with it?Aideaidedecamp
CORS can be an issue, because we are downloading the image again via ajax (well, if it's already loaded on the page, it would be taken from the cache). But the error is not related to this.Brittain
@UNlessofficialchannel, updated the original function and added a React sample. (async/await were missing from copyToClipboard function)Brittain
@YTG , The example is not going to work on here or sites like JSFiddle as the iframes need to have the "clipboard-write" permissions set.Brittain
V
1

You can use navigator.clipboard.write

async function copyImg(src) {
   const img = await fetch(src);
   const imgBlob = await img.blob();
   try {
      navigator.clipboard.write([
        new ClipboardItem({
            'image/png': imgBlob, // change image type accordingly
        })
      ]);
    } catch (error) {
        console.error(error);
    }
}
Vala answered 4/12, 2019 at 18:47 Comment(5)
You have a typo, should be async function.Brittain
This should work, providing the images are on the same domain, or on a domain with CORS disabled.Brittain
The website images are jpg not png.Aideaidedecamp
then use image/jpgVala
Write type image/jpeg not supported unfortunately.Brittain
C
0

You can try this. You need to provide an HTMLDivElement to this.

It is genereally a ref to a certain div.

<div ref={node => (this._imageRef = node)}>
 <img src=""/>
</div>

You can initialize this red in the constructor as

 constructor(props) {
    super(props);

    this._imageRef = null;
  }

You need to provide this _imageRef to the function.

Now all this should work.

export function copyImageToClipboard(element) { // element is an ref to the div here
  const selection = window.getSelection();
  const range = document.createRange();
  const img = element.firstChild ;

  // Preserve alternate text
  const altText = img.alt;
  img.setAttribute('alt', img.src);

  range.selectNodeContents(element);
  selection.removeAllRanges();
  selection.addRange(range);

  try {
    // Security exception may be thrown by some browsers.
    return document.execCommand('copy');
  } catch (ex) {
    console.warn('Copy to clipboard failed.', ex);

    return false;
  } finally {
    img.setAttribute('alt', altText);
  }
}

Note: This Works in IE as well

Catatonia answered 5/12, 2019 at 8:55 Comment(1)
I tried it here but I am not able to paste it in slack, or stackoverflow etc. - jsfiddle.net/xz14jp3f. Anu suggestions?Graybeard
P
0

Using Typescript

const copyImageToClipboard = async (imageURL?: string) => {
        if (imageURL === undefined || imageURL === null) return;

        try {
            const image = await fetch(imageURL!);
            const imageBlob = await image.blob();

            await navigator.clipboard
                .write([
                    new ClipboardItem({
                        'image/png': imageBlob,
                    })
                ]);
        } catch (error) {
            console.error(error);
        }
    }
Pegboard answered 24/6, 2022 at 15:49 Comment(0)
S
0

All the answers above look too complicated to me, especially the ones that involved canvas. I think this is a more direct approach. This works only on PNG images.

First of all, you use the fetch API to get the image, and then you create a blob object from the image. Later on, you just use the clipboard API, that accepts the blob as a parameter!

export const copyImage = (imagePath) => {
  fetch(imagePath)
    .then((response) => response.blob())
    .then((blob) => {
      // Now you have the image data as a blob object

      navigator.clipboard.write([
        new ClipboardItem({
          "image/png": blob,
        }),
      ]);
    })
    .catch((error) => console.error("Error fetching image:", error));
};
Suzannsuzanna answered 27/11, 2023 at 16:16 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.