这篇文章发布于 2023年09月17日,星期日,16:06,归类于 JS实例。 阅读 31201 次, 今日 32 次 16 条评论
by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=11015 鑫空间-鑫生活
本文欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可以联系授权。

一、总要有原因的
为什么会突然想到了解如何使用JS复制图片到剪切板呢?
因为上周弄了个PNG/JP在线压缩的工具,一开始的时候,图片获取的途径只能是下载到本地。
但很多时候,我是希望这种压缩好的图片可以直接上传,不需要再从本地转一圈。
此时,如果有一个复制功能就好了。
直接点击一个按钮,压缩好的图片复制到剪切板,然后再去上传页面进行上传,多方便,多轻松。
所以,就去了解了下,还是学到了不少东西的。
这里和大家分享下。
首先,先从简单的开始。
二、原生的图片复制API
浏览器提供了一个名为 ClipboardItem 的实例对象,可以构造构造剪切板复制对象。
例如一段文本的复制(代码取自MDN文档):
function setClipboard(text) {
const type = "text/plain";
const blob = new Blob([text], { type });
const data = [new ClipboardItem({ [type]: blob })];
navigator.clipboard.write(data).then(() => {
/* success */
}, () => {
/* failure */
});
}
图片也不在话下。
我专门写了个复制图片的方法,参数就是页面中的IMG元素对象。
const doCopyImg2Clipboard = async (image, success = () => {}, failure = () => {}) => {
if (!image || !image.src) {
return;
}
// 需要复制的图片的地址
const src = image.src;
// 请求该地址,返回图片的blob数据
const response = await fetch(src);
// 需要是blob数据格式
const blob = response.blob();
// 使用剪切板API进行复制
const data = [new ClipboardItem({
[blob.type || 'image/' + src.split('.').slice(-1)[0].replace('jpg', 'jpeg')]: blob
})];
navigator.clipboard.write(data).then(success, failure);
}
如果你已经有了图片的数据(例如本地上传、拖拽或粘贴的图片),则fetch步骤可以略过。
所以,当页面中有如下所示的HTML代码(一张图,一个按钮):
<button id="button" type="primary">复制图片</button> <img id="image" src="./mybook.jpg">
就可以使用一个简单的点击事件,触发图片的复制了。
例如:
// 点击按钮进行复制
button.addEventListener('click', () => {
doCopyImg2Clipboard(image, function () {
new LightTip('复制成功', 'success');
}, function (err) {
new LightTip('复制失败:' + err, 'error');
});
});
然而,上面的代码得到的结果却是失败!

从上面的错误提示可以看出,浏览器是不支持 jpg 格式的图片信息写入剪切板的。
试试PNG呢?
如果我们把图片的JPG格式,转换为PNG格式,再试一试,比方说:
<button id="button" type="primary">复制图片</button> <img id="image" src="./mybook.png">
则就可以成功复制!
眼见为实,您可以狠狠地点击这里:复制JPG和PNG图片到剪切板是否成功demo
比方说,我们点击demo右侧的那个按钮,此时,提示的就会是复制成功,如下截图示意。

此时,PNG图片信息就已经在剪切板中了。
我们可以找个富文本输入框,或者在线文档编辑器等进行测试。
例如demo页面提供的输入框,我们执行下Ctrl+V,则可以看到图片出现了,如下图所示。

OK,好,下面问题来了,页面中有些图片他就是JPG格式的,我非要复制,有没有什么办法呢?
三、JPG/WebP图片的解决之道
要想让JPG或者webp格式的图片也支持剪切板复制,很简单,转为PNG无损图就好了呀。
例如,绘制在canvas上,再使用canvas的toBlob()方法转一下就噢啦。
所以,上面提供的复制代码,我们还可以进一步增强下。
完整JS代码如下所示。
const doCopyImg2Clipboard = async (image, success = () => {}, failure = () => {}) => {
if (!image || !image.src) {
return;
}
// 原图尺寸
const { naturalWidth, naturalHeight } = image;
if (!naturalWidth) {
failure('图片尚未成功加载');
return;
}
// 绘制图片到canvas上
const canvas = document.createElement('canvas');
canvas.width = naturalWidth;
canvas.height = naturalHeight;
// canvas绘制上下文
const context = canvas.getContext('2d');
// 图片绘制
context.drawImage(image, 0, 0, naturalWidth, naturalHeight);
// 转为Blob数据
canvas.toBlob(blob => {
// 使用剪切板API进行复制
const data = [new ClipboardItem({
['image/png']: blob
})];
navigator.clipboard.write(data).then(success, failure);
});
}
此时,无论是JPG图片还是WebP图片都可以复制到剪切板了。
doCopyImg2Clipboard(image, function () {
console.log('复制成功');
});
有演示页面可以体验,您可以狠狠地点击这里:JS复制JPG图片到剪切板demo

然而,虽然图片复制了,但是变成PNG之后,原本尺寸适合的JPG图片尺寸就会变得很大。
那图片等于白压缩了。
所以,如果希望复制JPG图片的时候,保留该JPG原始的尺寸,该怎么办呢?
四、若想要保留原图尺寸?
解决方法是不是复制blob二进制数据,而是复制表示图片数据的base64字符串。
然后在上传或者粘贴的地方再二次处理。
图片转base64并复制到剪切板
不哔哔,直接看代码,使用FileReader对象将blob数据转为base64。
const doCopyImgBase64Clipboard = (image, success = () => {}, failure = () => {}) => {
if (!image || !image.src) {
return;
}
// 需要复制的图片的地址
const src = image.src;
// 请求该地址,返回图片的blob数据
fetch(src).then(response => response.blob()).then(blob => {
// blob数据转base64
const reader = new FileReader();
reader.onload = function() {
navigator.clipboard.writeText(this.result).then(success, failure);
};
reader.readAsDataURL(blob) ;
});
}
其中:
- image
- 表示页面中的IMG元素对象。
- success
- 表示成功的回调
- failure
- 表示失败的回调
此时,就可以点击按钮,一键复制。
base64图片的处理
在这场场景下,复制并不是最难的,因为文本复制有着非常成熟的解决方案,比方说我之前弄了个“极简文字内容剪切板复制”的gitee项目,优点是自带复制成功的交互提示。
也可以使用业界知名的clipboard.js:https://github.com/zenorocha/clipboard.js
难点在于粘贴时候对base64文本的处理,因为处理粘贴文本并不是一个经常会遇到的场景,大多数的前端都缺乏相应的处理经验。
这里,通过两个例子,演示预览和上传的处理。
1. 预览
base64本身就可以作为图片的URL地址,因此,预览相对简单,只要创建个图片DOM元素,然后插入到页面中就好了。
那如何获得剪切板中粘贴的内容呢?有对应的API的,代码示意:
// 在输入框中粘贴
input.addEventListener('paste', event => {
var paste = event.clipboardData?.getData('text') || '';
});
// 在页面中粘贴
document.addEventListener('paste', event => {
var paste = event.clipboardData?.getData('text') || '';
});
此时,只要判断 paste 内容匹配 base64 图片格式即可。
以在输入框中粘贴base64图片为例,完整交互JavaScript代码参见:
// 粘贴对base64内容的监控处理
input.addEventListener('paste', event => {
var clipboardData = event.clipboardData;
var paste = clipboardData?.getData('text') || '';
if (!/^data:image\/[a-z]+;base64,/.test(paste)) {
return;
}
event.preventDefault();
// 我是范围
const selection = document.getSelection();
// 我是选区
const range = selection.getRangeAt(0);
// 删除选区内容,
range.deleteContents();
// 并替换成图片
const imgNode = document.createElement('img');
imgNode.src = paste;
range.insertNode(imgNode);
// 光标定位在图片后面
range.setStartAfter(imgNode);
});
眼见为实,您可以狠狠地点击这里:JS复制图片base64信息到剪切板demo
点击按钮,会复制图片的base64信息,此时,再粘贴到页面中的测试输入框,会发现,显示的不是字符串内容,而是图片,就是因为有上面这段 JS 解析代码。

2. 上传
base64图片地址上传有两种解决方法,一是后端判断处理,即,如果post的是二进制数据流,如何处理,如果post的是字符串数据流,又该如何处理。
还有一种方法是在前端处理,将剪切板中的base64字符串转为blob文件数据再上传,这样,后端那边轻松点。
以下代码示意了下如何处理:
// base64 转 blob
const b64toBlob = (b64Data, contentType='', sliceSize=512) => {
const byteCharacters = atob(b64Data);
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
const blob = new Blob(byteArrays, {type: contentType});
return blob;
}
// 粘贴base64上传
document.addEventListener('paste', function (event) {
var clipboardData = event.clipboardData;
var paste = clipboardData?.getData('text') || '';
if (!paste.startsWith('data:image')) {
return;
}
// 核心数据
var base64 = paste.split('base64,')[1];
// 类型
var type = paste.split(';base64')[0].replace('data:', '');
// 获取 blob 数据
var blob = b64toBlob(base64, type);
// 设置个文件名,不然是undefined
blob.name = new Date().toLocaleString().replaceAll('/', '-').replace(/\s/g, '_').replaceAll(':', '') + '.' + (type.split('/')[1] || 'png');
// 此时的 blob 就是需要上传的文件数据了...
});
于是,整个一套流程就可以走通了。
前端压缩图片,JS复制压缩后的base64字符信息,然后JS解析base64并上传。
结语
时不时给自己找个需求,而不仅仅是业务开发,可能会遇到平常遇不到的场景,积累不一样的经验。
要是下次业务开发遇到类似需求,那就是分分钟上手,开发效率领先一步。
还能撸一篇文章,完成自己给自己制定的平均每周需要一个技术文章输出的KPI。
一劳多得,岂不美哉。
噢啦,以上就是本文的全部内容啦。
希望里面的内容可以对大家的工作学习有所帮助。
如果您觉得不错,欢迎,点赞。
? ? ? ? ?
本文为原创文章,欢迎分享,勿全文转载,如果实在喜欢,可收藏,永不过期,且会及时更新知识点及修正错误,阅读体验也更好。
本文地址:https://www.zhangxinxu.com/wordpress/?p=11015
(本篇完)
- 直接剪切板粘贴上传图片的前端JS实现 (0.676)
- 利用剪切板JS API优化输入框的粘贴体验 (0.411)
- JS复制文字到剪切板的极简实现及扩展 (0.411)
- 纯前端实现可传图可字幕台词定制的GIF表情生成器 (0.354)
- 学习了,CSS中内联SVG图片有比Base64更好的形式 (0.299)
- 基于HTML5的可预览多图片Ajax上传 (0.265)
- 本地MP3封面图、时长等信息的JS读取 (0.260)
- 瞎折腾,使用JS让中文内容莫名其妙、狗屁不通 (0.205)
- 小tips:使用canvas在前端实现图片水印合成 (0.187)
- 原来浏览器原生支持JS Base64编码解码 (0.187)
- SVG <foreignObject>简介与截图等应用 (RANDOM - 0.093)
其实配合拖拽上传文件可以直接排列无需复制粘贴,也更方便一些
要复制多张图片的场景呢?
两个示例都不能在safari中复制成功,大佬可以试一下,Safari这个限制如何解决
我也写过类似的文章,分享给大家
js:浏览器环境下复制图片到剪切板
//pengshiyu.blog.csdn.net/article/details/131901909
求助!!!clipboard在没有域名的网站里使用复制不到内容怎么解决?IP路径的网站中判断navigator.clipboard是为真的,但是,navigator.clipboard.writeText(str),writeText后没有将str里的字符串复制到剪切板里。这是为什么呢?
可能权限没给吧,需要还传统方法,可以试试我这个项目:https://gitee.com/zhangxinxu/copy-text
估计无法脱离https
为啥我手机浏览器demo里png和jpg都报一样的权限错误
我也遇到了,都不行
看来我比大佬强一点点,这个功能,我之前就学会了!
毕竟学无止境
那gif的咋复制啊
只能base64复制,否则只有一帧PNG图。
有没有可能可以直接右键复制,copy这个base64总感觉鸡肋
右键复制是PNG无损图。
为什么浏览器只能复制出PNG图?又可以写一篇文章了。手动斜眼笑