一键将HTML转换成图片复制到剪切板上功能的实现

一键实现HTML转换成图片复制到剪切板上功能

背景:

项目截图可能由于用户电脑分辨率的问题无法截全图(排除用长截屏工具的情况下)

思路:

把html结构先用canvas转换成图片,再使用Clipboard API实现复制到剪切板的功能

在此过程中一共会有两种方法可以实现当前功能(Document.execCommand() 方法以及Clipboard API)

①因为Document.execCommand()方法虽然方便,但是有一些缺点。首先,它只能将选中的内容复制到剪贴板,无法向剪贴板任意写入内容。

其次,它是同步操作,如果复制/粘贴大量数据,页面会出现卡顿。有些浏览器还会跳出提示框,要求用户许可,这时在用户做出选择前,页面会失去响应。

②相较于Document.execCommand()方法,它的所有操作都是异步的,调用是会返回 Promise 对象,不会造成页面卡顿。而且,它可以将任意内容(比如图片)放入剪贴板。navigator.clipboard属性返回 Clipboard 对象,所有操作都通过这个对象进行。

但是clipboard允许脚本任意读取会产生安全风险,所以这个 API 的安全限制比较多。

考虑到稳定性,所以我选择了用clipboard去实现这个功能

想更多了解内容可点击链接前往阮老师的博客观看详细内容 =>

实现步骤:

1.首先将html转换为图片需要html2canvas插件帮忙

npm install html2canvas
或
yarn add html2canvas

安装成功后,在对应页面引入插件

import html2canvas from "html2canvas"

2.写页面代码

<Button onClick={() => saveCanvas()}>复制</Button>
<div style={{display:"flex",justifyContent:"center",alignItems:"center"}}>这是测试用例</div>

3.通过将html结构转换为canvas对象

const saveCanvas =()=>{
      const scale = window.devicePixelRatio; // 当前显示设备的物理像素分辨率与CSS像素分辨率之比
      html2canvas(document.querySelector("#qContent"), {
                // dpi: 300,   // 精度,处理模糊问题
                useCORS: true, // 允许跨域
                scale: scale,  // 当前显示设备的物理像素分辨率与CSS像素分辨率之比
                width: 1300,   //图片宽度可自行设置
                height: 600,   //图片高度可自行设置
                backgroundColor: "#fff", //图片背景色
                allowTaint: true, //若是 useCORS:true,且同时设置allowTaint为true,仍然会认为画布已被污染而不可用。
                removeContainer: true // 清除临时创建的克隆dom元素
                // eslint-disable-next-line space-before-function-paren
      }).then(async (canvas) => {
         const base64Img = canvas.toDataURL("image/png"); // 通过toDataURL将此canvas对象转成base64编码
         const file = base64toFile(base64Img, "图片"); // 转file
         copyFile(file);
      });
}

4.然后把它转换为base64文件的格式

 const base64toFile = (dataBase64, filename = "file") => {
        const arr = dataBase64.split(",");
        const mime = arr[0].match(/:(.*?);/)[1]; // 获取file文件流的type名称
        const suffix = mime.split("/")[1]; // 获取文件类型
        const bstr = window.atob(arr[1]);
        let n = bstr.length;
        const u8arr = new Uint8Array(n);
        while (n--) {
            u8arr[n] = bstr.charCodeAt(n);
        }
        return new File([u8arr], `${filename}.${suffix}`, {
            type: mime
        });
    };

5.在上面已经获得了html结构转图片后的base64文件,将其转换成blob对象,再通过剪贴板的navigator.clipboard.write()方法写入

  const copyFile = (file) => {
        const reader = new FileReader();
        reader.onload = (e) => {
            const newFile = e.target.result.toString(); 
            const img = new Image();
            img.src = newFile;
            img.onload = () => {
                //blob对象在写入时可能会出现格式出题,所以先将图片解析成base64,在转换成blob对象写入,减少后面不必要的麻烦
                let base64 = imageBase64(img);
                let blob = base64ToBlob(base64.replace("data:image/png;base64,", ""), "image/png", 512);
                navigator.clipboard.write([
                    new ClipboardItem({
                        "image/png": blob
                    })
                ]);
            };
        };
        reader.readAsDataURL(file);
    };

注意:首先,Chrome 浏览器规定,clipboard API只有 HTTPS 协议的页面才能使用这个 API。不过,开发环境(localhost)允许使用非加密协议。其次,调用时需要明确获得用户的许可。权限的具体实现使用了 Permissions API,跟剪贴板相关的有两个权限:clipboard-write(写权限)和clipboard-read(读权限)。总而言之言而总之就是讲内容写到剪切板脚本可以完成,但是读取剪切板时就需要用户的同意才可以继续使用了。

//先将图片解析成base64 
const imageBase64 = (img) => {
        let canvas = document.createElement("canvas");
        canvas.width = img.width;
        canvas.height = img.height;
        let ctx = canvas.getContext("2d");
        ctx.drawImage(img, 0, 0, img.width, img.height);
        let dataURL = canvas.toDataURL("image/png");
        return dataURL;
    };
 // 再转成blod // 注意base64里面的特殊符号 在atob的时候不能识别“,”
    const base64ToBlob = (b64Data, contentType, sliceSize) => {
        contentType = contentType || "";
        sliceSize = sliceSize || 512;
        let byteCharacters = window.atob(b64Data);
        // var byteCharacters  = b64Data;
        // 该atob函数将base64编码的字符串解码为一个新字符串,其中包含二进制数据每个字节的字符。
        let byteArrays = [];
        for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
            let slice = byteCharacters.slice(offset, offset + sliceSize);
            let byteNumbers = new Array(slice.length);
            // 通过使用.charCodeAt字符串中每个字符的方法应用它来创建一个新的数组。
            for (let i = 0; i < slice.length; i++) {
                byteNumbers[i] = slice.charCodeAt(i);
            }
            // 将这个数组转换为实际类型的数组,方法是将其传递给Uint8Array构造函数。
            let byteArray = new Uint8Array(byteNumbers);
            byteArrays.push(byteArray);
        }
        // 创建一个blob:包含这条数据的URL,返回去。
        let blob = new Blob(byteArrays, { type: contentType });
        return blob;
    };

最后转来转去也终于是实现了功能,第一次写帖子,也欢迎大佬们指导和纠正。

原文链接:https://juejin.cn/post/7262274383288369212 作者:KyrieZhangi

(0)
上一篇 2023年8月2日 上午10:36
下一篇 2023年8月2日 上午10:47

相关推荐

发表回复

登录后才能评论