最近开发中遇到一个需求,就是用户希望能通过直接点击按钮复制图片,然后就可以很方便的把图片发送到班群中,于是就有了复制图片的需求。
那么如何通过JavaScript
来实现复制图片呢?
一、前置知识:如何实现复制?
既然要复制图片,那我们先看看前端是怎么实现复制功能的。
一般来说,实现复制有2种方案:
document.execCommand()
方法;Clipboard API
;
复制功能的安全限制:复制脚本需要放在监听事件回调里面进行执行,由用户触发(比如点击事件),如果直接执行脚本,浏览器可能会报错。
下面分别介绍下这2个方法:
1.1 document.execCommand()
使用方法:
先选中文本,然后调用 document.execCommand('copy')
,即可将选中的文本复制到剪贴板。
const input = document.getElementById("input");
input.select(); // 注意 input 框不能添加 disabled 属性,否则会影响 select() 方法
document.execCommand("copy");
同样的它还可以实现剪切
和粘贴
功能。
document.execCommand('cut')
:剪切选中的文本到剪贴板。document.execCommand('paste')
:从剪贴板粘贴文本到光标位置。
剪切功能的使用和复制一样,所以这里就不做演示了。我这边再演示一下粘贴功能。
粘贴功能有以下使用限制:
- 确保元素是可编辑的,比如
input
、textarea
、contenteditable
属性为true
的元素。 - 确保元素是焦点元素,可以使用
focus()
方法将焦点设置到元素上。
const target = document.getElementById('pasteTargetInput');
target.focus(); // 确保目标元素获得焦点
document.execCommand('paste'); // 尝试粘贴
但是现代浏览器比如(Chrome、Firefox、Edge
)已经限制或废弃了 execCommand("paste")
的直接使用,所以不推荐用这个api了。
最后总结一下,document.execCommand()
是实现复制的一个传统方法,它的优缺点如下:
- 优点:低版本浏览器兼容性好,
- 缺点:
- 已被
web 标准
弃用,不推荐使用。 - 只能复制文本,不能复制图片或者二进制数据。
- 只能讲选中的内容写入到剪贴板,不能直接写入自定义内容。
- 它是同步操作,如果复制内容过多,会引起页面卡顿。
1.2 Clipboard API
在浏览器的BOM
对象中,有一个API
叫clipboard
,它提供了系统剪贴板的读写访问能力,可以实现剪切、复制和粘贴功能
,我们可以通过window
上的navigator
对象直接访问到一个clipboard
对象。
console.log(navigator.clipboard);
clipboard
提供了四个方法:
read()
:从剪切板读取数据,它会返回一个Promise
,并将数据包装成一个ClipboardItem
对象回传,readText()
:从剪切板中读取文本
,它会返回一个Promise
对象,并将剪切板数据作为String
回传,write()
:写入数据(ClipboardItem对象
)到剪切板,它会返回一个Promise
,Promise
成功意味着写入完成,writeText()
:写入文本
到剪切板,它会返回一个Promise
,Promise
成功意味着写入完成。
我们可以通过上面描述的write()
和writeText()
方法实现复制功能,writeText()
只能写入文本,而write()
方法则可以写入任意数据,所以我们需要使用write()
方法实现复制图片的功能。
二、实现复制图片
一般图片是通过两种形式加载:
- 远程URL,最常见的是
cdn
地址,比如https://cdn.xxx.cn/example.png
, - base64 URL,一些体积较小的图片通常会打包成
base64
字符串,以减少网站资源请求数量,比如data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...
。
不管是什么图片形式的图片,我们最终目的都是需要将它转化为blob
对象,然后通过navigator.clipboard.write()
方法写入到剪贴板。
比如这样一张图片:
<img src="https://xxx" alt="加载失败" id="img"/>
我们可以通过 canvas
将图片复制到剪贴板,具体代码如下:
function copyImage(img) {
const canvas = document.createElement('canvas')
canvas.width = img.width
canvas.height = img.height
const ctx = canvas.getContext('2d')
ctx.drawImage(img, 0, 0, img.width, img.height)
canvas.toBlob((blob) => {
// 目前图片只支持 png 类型
const clipboardItem = new ClipboardItem({ 'image/png': blob })
navigator.clipboard.write([clipboardItem])
})
}
img.addEventListener("click", () => {
copyImage(img);
});
这里如果出现Uncaught SecurityError: Failed to execute 'toBlob' on 'HTMLCanvasElement'
报错,可能是你使用的图片跨域了,首先确保图片资源服务器允许跨域使用,然后需要在img
标签上增加crossOrigin
属性。
<img src="xxx" crossOrigin/>
当然实际业务中可能提供的只是一个图片点击后的跳转链接,需要先把链接转成一张二维码图片,再进行复制。
这里使用第三方库qrcode
,把链接转化成二维码。
import QRcode from 'qrcode';
async function copyImage(url) {
// 转成 base64 的 url
const url = await QRcode.toDataURL(link, {
errorCorrectionLevel: 'H', // 纠错级别最高
width: 128,
margin: 2,
scale: 1,
});
const blob = await (await fetch(url)).blob();
const clipboardItem = new ClipboardItem({ 'image/png': blob })
navigator.clipboard.write([clipboardItem])
}
我们这里通过QRcode.toDataURL()
方法,拿到二维码的base64
图片url,然后通过原生的fetch
方法,把二维码图片转化成blob
对象,最后以ClipboardItem
形式写入到clipboard
中。
除了fetch
以外,还可以通过如下方式将base64
图片转化为blob
对象,具体代码如下:
function dataURIToBlob(dataURI) {
const base64Index = dataURI.indexOf(';base64,') + 8;
const base64 = dataURI.substring(base64Index);
const byteCharacters = atob(base64);
const byteArrays = [];
for (let i = 0; i < byteCharacters.length; i++) {
byteArrays.push(byteCharacters.charCodeAt(i));
}
const byteArray = new Uint8Array(byteArrays);
const blob = new Blob([byteArray], { type: 'image/png' });
return blob;
}
三、clipboard兼容性问题
在一些浏览器上可能不支持Clipboard API
,所以需要通过navigator.clipboard
的值是否为undefined
判断此浏览器是否支持复制功能,如果不支持Clipboard API
的话,就无法复制图片了,这时候可以使用document.execCommand('copy')
来复制文本,或者直接给用户提示该浏览器不支持复制图片。
// 将dataBase64复制到剪切板
function copyToClipboard(text) {
let a = document.createElement('input');
a.value = text;
document.body.appendChild(a);
a.select();
document.execCommand('copy');
a.remove();
}
function copyImage() {
if (navigator.clipboard) {
//...
const clipboardItem = new ClipboardItem({ 'image/png': imgBlob });
navigator.clipboard.write([clipboardItem]);
console.log('复制成功');
} else {
copyToClipboard(img.src);
}
}
四、其它功能
4.1 预览图片
预览图片主要依赖于URL.createObjectURL
这个原生API
,具体代码如下:
function previewImage(img) {
const url = URL.createObjectURL(blob);
const img = document.createElement('img');
img.src = url;
document.body.appendChild(img);
}
4.2 下载图片
下载图片主要依赖于a
标签的download
属性,具体代码如下:
function downloadImage(img) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = true;
a.click();
URL.revokeObjectURL(url);
console.log('下载图片成功');
}
五、小结
本文主要介绍了前端如何实现复制图片
的功能,核心是通过Clipboard API
来实现的,如遇到浏览器兼容问题,可以考虑回退使用document.execCommand()
进行低版本的兼容,另外还介绍了预览图片
和下载图片
的实现思路,希望能帮助到大家!