preface

A while ago, I received a development task, is to copy the pictures on the web page, and paste into the chat tool. This is a very common operation, in daily work we often through QQ screenshot, Ctrl + C to copy the picture to the chat box.

Github has several high star JS copy libraries, such asclipboard.js,copy-to-clipboardAnd so on, its implementation principle is selected after the element, calldocument.exceCommand('copy')This API implements replication, but usually only supports itText copiedAnd the API is obsolete. Therefore, I decided to package a small component to replicate images in React.

Receives clipboard content

Although we don’t need to implement the clipboard -> paste process, but to clarify the data needed to copy the clipboard, we can briefly understand how the input box receives data. What kind of data is it receiving?

  • The basic principle is through listeningpasteEvent, interceptedclipboardDataData (notepasteEvents can only occur incontenteditable: trueTriggered on the element)
  • throughreader.readAsDataURLAchieve the generation of preview pictures, or directly upload picture data to the back end
/ / code reference: https://cloud.tencent.com/document/product/269/37448
document
    .getElementById("testPasteInput")
    .addEventListener("paste".function (e) {
      let clipboardData = e.clipboardData;
      let file;
      if (
        clipboardData &&
        clipboardData.files &&
        clipboardData.files.length > 0
      ) {
        file = clipboardData.files[0];
        var reader = new FileReader();
        reader.onload = function (e) {
          var image = document.getElementById("result");
          image.src = e.target.result;
          image.style.display = "block";
        };
        reader.readAsDataURL(file);
      }
      if (typeof file === "undefined") {
        console.warn("File is undefined, please check code or browser compatibility!");
        return; }});Copy the code

Through the code, we learn that we receive File/Blob type content from the clipboard in the input box via listening events.

Now that we know how messages are received, we need to figure out how to add the target content to the clipboard.

There are two main apis currently available:

  • excecommand
  • Clipboard API

Copy content to clipboard using Excecommand

The selected elements

The key to using Excecommand (‘copy’) is that we want to implement the effect of the selected element. When we think of selection, we usually think of the input select() event, but not all elements have this interface. A more general API is the Range API. We can use this API to select elements in the page or dynamically generate an element and select.

function selectFakeInput(input: HTMLELEMENT) {
  let selection = window.getSelection();
  let range = document.createRange(); range.selectNode(input); selection! .removeAllRanges(); selection! .addRange(range);return () = >{ selection! .removeAllRanges(); }; }Copy the code

Write clipboard

 let success = document.execCommand("copy");
 if(! success) {throw Error("Replication failed!");
 }
Copy the code

Practice has found that this API does a good job of copying text, while image copying often fails

Copy content to the Clipboard using the Clipboard API

Convert the target content to Blob type

//text
function textToBlob(target: string = "") {
  return new Blob([target], { type: "text/plain" });
}
// image SRC (image source must be cross-domain)
function imageToBlob(target: string = "") {
   const response = await fetch(target);
   return await response.blob();
}
Copy the code

Ask your browser if it supports it

async function isSupportClipboardWrite() {
  try {
    const permission = awaitnavigator.permissions? .query? ({.//@ts-ignore
      name: "clipboard-write".allowWithoutGesture: false});returnpermission? .state ==="granted";
  } catch (error) {
    return false; }}Copy the code

Write clipboard

const data = [new window.ClipboardItem({ [blob.type]: blob })];
await navigator.clipboard.write(data);
Copy the code

Encapsulate the React component

Here, we try to use hooks encapsulation.

export const useCopyImage = (props: ClipboardImageHooksProps) = > {
  const [status, setStatus] = useState<ChangeStatus>(null);
  const [err, setErr] = useState<Error> (null);
  const copyImage = useCallback(async (target: ImageCopyTarget) => {
    try {
      setErr(null);
      setStatus("loading");
      const canWrite = await isSupportClipboardWrite();
      if (
        !canWrite ||
        !window.ClipboardItem || ! navigator.clipboard? .write ) {throw Error("broswer not supported!");
      }
      const blob = await imageToBlob(target);
      const data = [new window.ClipboardItem({ [blob.type]: blob })];
      await navigator.clipboard.write(data);
      setStatus("done");
    } catch (err) {
      setStatus("error"); setErr(err); }} []);return { status, error: err, copy:copyImage };
};
Copy the code

Can be used directly after packaging

import { useCopyText } from "rc-clipboard-copy";
const App = () = > {
  const { copy, error, status } = useCopyText({});
  return (
    <div>
      <button onClick={()= > copy("hello word 2")}>copy text</button>
    </div>
  );
};
Copy the code

conclusion

See the Github repository for details

The resources

  • Direct clipboard paste upload picture front-end JS implementation
  • Messaging (Web & Applets)
  • Interact with the clipboard