4

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

Itay Tur
  • 683
  • 1
  • 15
  • 40
  • Change `copyToClipboard` to `copyService.copyToClipboard`, and `createImage({ src: imageUrl });` to `copyService.createImage({ src: imageUrl });` – dw_ Dec 05 '19 at 14:00
  • Which line triggers the `Uncaught in promise` error? – dw_ Dec 05 '19 at 14:20
  • navigator.clipboard.write – Itay Tur Dec 05 '19 at 14:23
  • https://jsfiddle.net/48trLf0q/ Does this jsfiddle work for you? – dw_ Dec 05 '19 at 14:36
  • 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. – Itay Tur Dec 06 '19 at 12:47

4 Answers4

5

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.
dw_
  • 1,707
  • 1
  • 7
  • 13
  • 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? – Itay Tur Dec 05 '19 at 08:56
  • 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. – dw_ Dec 05 '19 at 14:06
  • @UNlessofficialchannel, updated the original function and added a React sample. (async/await were missing from `copyToClipboard` function) – dw_ Dec 05 '19 at 15:04
  • @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. – dw_ Apr 06 '21 at 15:53
2

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);
    }
}
adolfosrs
  • 9,286
  • 5
  • 39
  • 67
Zohaib Ijaz
  • 21,926
  • 7
  • 38
  • 60
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

Sagar Acharya
  • 1,763
  • 2
  • 19
  • 38
  • I tried it here but I am not able to paste it in slack, or stackoverflow etc. - http://jsfiddle.net/xz14jp3f/. Anu suggestions? – Govinda Totla Aug 31 '20 at 05:08
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);
        }
    }
Pramodya Abeysinghe
  • 1,098
  • 17
  • 13