JS - 获取剪切板内容 Clipboard API

1,需求

一个问题咨询表单页,可以上传图片。因为有截图的场景,所以需要得先截图保存在本地再上传,比较繁琐。

想从剪切板获取截图后,直接上传。

最终效果

在这里插入图片描述

2,实现

从剪切板获取的内容分为文本和非文本,分别对应2个API,

获取的内容是 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.1http://localhost

http 环境下是 undefined

在这里插入图片描述

2,只能读取当前页面的剪切板

有5个区域:

  1. 页面内容显示区域
  2. 地址栏
  3. 书签栏
  4. 控制台
  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/plaintext/html

在这里插入图片描述

2,图片内容

1,如果是截图,type 统一为 image/png

在这里插入图片描述

2,如果是从网页上复制的图片(无论原图片是什么格式),type 统一都是2个:text/htmlimage/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>

以上。

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

下雪天的夏风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值