目录
- 1,需求
- 最终效果
- 2,实现
- 示例
- 3,注意点
- 1,只支持安全上下文环境
- 2,只能读取当前页面的剪切板
- 3,权限获取问题
- 4,获取内容的 MIME_TYPE 问题
- 1,文本内容
- 2,图片内容
- 5,只能获取剪切板内容的 blob 类型
- 6,`URL.revokeObjectURL` 的时机
- 4,其他
- paste 事件简单举例
1,需求
一个问题咨询表单页,可以上传图片。因为有截图的场景,所以需要得先截图保存在本地再上传,比较繁琐。
想从剪切板获取截图后,直接上传。
最终效果
2,实现
从剪切板获取的内容分为文本和非文本,分别对应2个API,
- navigator.clipboard.readText()
- navigator.clipboard.read(),可以用它来获取图片。
获取的内容是 ClipboardItem 对象。
示例
完整代码:
<template>
<div ref="refPasteBox"></div>
<button @click="getClipImg">获取剪切板图片</button>
</template>
<script setup lang="ts">
import { ref } from "vue";
const refPasteBox = ref<HTMLDivElement>();
const getClipImg = async () => {
try {
const clipboardContent = await navigator.clipboard.read();
const clipboardItem = clipboardContent[0];
let noImg = true;
for (const type of clipboardItem.types) {
if (type === "image/png") {
noImg = false;
const blob = await clipboardItem.getType(type);
const url = URL.createObjectURL(blob);
// 如果需要 File 对象
const file = new File([blob], "clipboard-image.png", { type });
console.log(file);
const img = document.createElement("img");
img.src = url;
img.width = 300;
img.onload = () => {
URL.revokeObjectURL(img.src);
};
refPasteBox.value?.appendChild(img);
}
}
if (noImg) {
alert("当前剪切板中没有图片。\n Windows 系统可通过快捷键\n ⌘+V \n查看剪切板");
}
} catch (err: any) {
if (err.name === "NotAllowedError") {
console.log("用户拒绝了访问剪贴板");
} else {
console.error("无法读取剪贴板内容: ", err);
}
}
};
</script>
3,注意点
1,只支持安全上下文环境
安全上下文,可以简单理解为只支持 https
协议和本地 http://127.0.0.1
、http://localhost
。
在 http
环境下是 undefined
,
2,只能读取当前页面的剪切板
有5个区域:
- 页面内容显示区域
- 地址栏
- 书签栏
- 控制台
- 其他应用
在执行 await navigator.clipboard.read()
相关 API 时,必须聚焦到区域1,否则会有如下报错!
正常情况下通过按钮点击来执行 API 时,都是满足的。
如果想在控制台中简单测试,可以用计时器。在控制台执行后,迅速点击页面区域就可以正常执行。
setTimeout(async () => {
const clipboardContent = await navigator.clipboard.read();
console.log(clipboardContent);
}, 2000);
3,权限获取问题
当第一次请求剪切板【读权限】时,也就是执行 await navigator.clipboard.read()
时,会弹出确认弹窗:
允许后就可以正常使用了。如果禁止了,无法通过再次执行代码打开该弹窗!只能手动重置权限。
4,获取内容的 MIME_TYPE 问题
const getClipImg = async () => {
try {
const clipboardContent = await navigator.clipboard.read();
const clipboardItem = clipboardContent[0];
console.log(clipboardItem);
for (const type of clipboardItem.types) {
if (type === "image/png") {
const blob = await clipboardItem.getType(type);
}
}
} catch (err) {
console.log(err)
}
};
示例代码中,通过 for 循环获取了剪切板内容的 type
,它有几个特点:
1,文本内容
无论是从什么地方手动复制的文本,type
都是2个:text/plain
和 text/html
。
2,图片内容
1,如果是截图,type
统一为 image/png
。
2,如果是从网页上复制的图片(无论原图片是什么格式),type
统一都是2个:text/html
和 image/png
。
所以,只需要判断 MIME 类型为 image/png
即可获取对应的图片。
另外,从本地复制的文件(图片,excel等) 无法通过
await navigator.clipboard.read()
API 获取。
5,只能获取剪切板内容的 blob 类型
// ...
for (const type of clipboardItem.types) {
if (type === "image/png") {
const blob = await clipboardItem.getType(type);
}
}
通过 getType 可以获取剪切板的内容,结果为 blob
类型,
如果需要预览,需要转换为 url
。
const url = URL.createObjectURL(blob);
如果需要 File 对象(比如上传),需要手动转换。
const file = new File([blob], "clipboard-image.png", { type });
6,URL.revokeObjectURL
的时机
if (type === "image/png") {
const blob = await clipboardItem.getType(type);
const url = URL.createObjectURL(blob);
const img = document.createElement("img");
img.src = url;
img.width = 300;
img.onload = () => {
URL.revokeObjectURL(img.src);
};
document.body.appendChild(img);
}
当通过 URL.createObjectURL() 创建可用于预览的对象 url
后,不能通过 URL.revokeObjectURL
立即释放该对象,否则图片无法显示。需要等到图片加载完成才行,或者不释放问题也不大。
4,其他
剪切板 API 获取的内容,和 paste 事件得到的内容是有区别的。
paste
事件一般用于富文本编辑,粘贴各种类型的文件。
- 剪切板不能获取本地复制的文件,
paste
事件可以。 - MIME 类型问题,从网页复制的是
type/html
,但却可以获取File
对象。为了兼容,应该用event.clipboardData?.files[0]
并加判断,而不是items[0].getAsFile()
, getAsFile-参考。 - 获取的 DataTransfer 对象,虽然 MDN 上的解释是拖动获取的内容,但其实也是粘贴事件获取的内容。
paste 事件简单举例
注意,ClipboardEvent
对象中部分内容通过 console.log
是看不到输出的,类似打印 currentTarget 得到的是 null
,但是可以使用的。
<template>
<div ref="refClipBox" class="clip-box" @paste="getPasteImage" contenteditable="true"></div>
</template>
<script setup lang="ts">
import { ref } from "vue";
const refClipBox = ref<HTMLDivElement>();
function getPasteImage(event: ClipboardEvent) {
event.preventDefault();
console.log(event);
// 检查剪贴板项目
const items = event.clipboardData?.items || [];
if (items[0].type.indexOf("image") === 0 || items[0].type === "text/html") {
// const blob = items[0].getAsFile();
const blob = event.clipboardData?.files[0];
const url = URL.createObjectURL(blob);
const img = document.createElement("img");
img.src = url;
img.width = 300;
img.onload = () => {
URL.revokeObjectURL(img.src);
};
refClipBox.value?.appendChild(img);
} else {
console.log("不是图片");
}
}
</script>
<style>
.clip-box {
width: 300px;
height: 300px;
border: 1px solid #000;
}
</style>
以上。
参考