Itay Tur
Itay Tur

Reputation: 1757

React - how to copy an image to clipboard?

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://stackoverflow.com/a/40547470/9608006

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://stackoverflow.com/a/59183698/9608006 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

Upvotes: 5

Views: 16596

Answers (5)

Jonathan
Jonathan

Reputation: 178

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));
};

Upvotes: 0

Pramodya Abeysinghe
Pramodya Abeysinghe

Reputation: 1210

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);
        }
    }

Upvotes: 0

Zohaib Ijaz
Zohaib Ijaz

Reputation: 22935

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);
    }
}

Upvotes: 1

dw_
dw_

Reputation: 1827

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.

Upvotes: 6

Sagar Acharya
Sagar Acharya

Reputation: 1881

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

Upvotes: 0

Related Questions