前言: 最近接到了一个需求很有意思,类似于我们经常在逛购物平台中,选择一个物品分享给好友,然后好友复制这段文本打开相对应的平台以后,就可以弹出链接上的物品。实现过程也比较有意思,特来分享一下实现思路🎁。
一. 效果预览
当我在别的界面复制了内容以后,回到主应用,要求可以检测到当前剪切板是什么内容。
二. 监听页面跳转动作
- 要完成这个需求,整体思路并不复杂。首先我们要解决的就是如何检测到用户从别的应用切回到我们自己的应用。
- 这个听起来很复杂,但其实浏览器已经提供了相对应的
api
来帮我们检测用户这个操作----document.visibilitychange
。 那么我们就可以写下如下代码
document.addEventListener("visibilitychange", () => { console.log("用户切换了"); });
相对应的效果如下图所示,你可能会好奇,我明明只切换了一次,但是为什么控制台却执行了两次打印?
这也不难理解,首先你要理解这个change
这个动作,你从 tab1 切换到 Tab2 的时候,触发了当前 Tab1 从可见 变为=> 不可见 。
而当你从 tab2 切回 tab1 的时候,触发了当前 Tab1 从不可见变为了可见。完整动作引起了状态两次变化,所以才有了两次打印。而我们的场景只是希望 app 从不可见转变为可见的时候才触发。那么我们就需要用到另外一个变量来配合使用-------
document.visibilityState
。
这个值是一个document
对象上的一个只读属性,它有三个string
类型的值visible
、hidden
、prerender
。从它的使用说明中不难看出,我们要使用的值是visible
。tips:hidden 可以用来配合做一些流量控制优化,当用户切换网页到后台的时候,我们可以停止一些不必要的轮询任务,待用户切回后再开启。
那么我们现在的代码应该是这样的:
document.addEventListener("visibilitychange", () => { if (document.visibilityState === "visible") { console.log("页面变得可见了!"); } });
可以看到,现在控制台正确的只执行了一次。
三. 完成读取剪切板内容
- 要完成读取剪切板内容需要用到浏览器提供的另外一个
api
-----navigator.clipboard
。这里穿插一个英语记忆的小技巧,我们要把这个单词分成两部分记忆:clip 和 board。clip 本身就有修剪的意思,board 常作为木板相近的含义和别的单词组合,如:黑板 blackboard、棋盘 chessboard。所以这两个单词组合起来的含义就是剪切板。 - 这里需要注意一句话,这个功能只能用在安全上下文中。这个概念很抽象,如果想深入了解的话,还需自行查阅资料。这里指简单说明这句话的限制:要想使用这个
api
你只能在localhost、127.0.0.1
这样的本地回环地址或者使用https
协议的网站中使用。 - 要快速检测当前浏览器或者网站是否是安全上下文 ,可以使用
Window:isSecureContext
属性来判断。 - 你可以动手访问一个 http 的网站,然后在控制台打印一下该属性,你大概率会看到一个
false
,则说明该环境不是一个安全上下文,所以 clipboard 在这个环境下大概率不会生效。因为本文章代码都为本地开发(localhost),所以自然为安全上下文。 - 经过上面的知识,那么我们就可以写出下面的兼容性代码。
- 前置步骤都已经完成,接下来就是具体读取剪切板内容了。关于读取操作,
clipboard
提供了两个api
-----read
和readText
。这里由于我们的需求很明确,我读取的链接本身就是一个字符串类型的数据,所以我们就直接选用readText
方法即可。稍后在第四章节我会介绍read
方法。 clipboard
所有操作都是异步会返回一个Promise
类型的数据的,所以这里我们的代码应该是这样的:document.addEventListener("visibilitychange", () => { if (document.visibilityState === "visible") { if (window.isSecureContext && navigator.clipboard) { const clipboardAPI = navigator.clipboard; //获取 clipboard 对象 setTimeout(() => { clipboardAPI.readText().then((text) => { console.log("text", text); }); }, 1000); } else { console.log("不支持 clipboard"); } } });
注意⚠️:这里你会看到我使用了
setTimeout
来解决演示的问题,如果你正在跟着练习但是不明白原因,请查看下面链接:
关于 DOM exception: document is not focused 请查阅stackoverflow 文档未聚焦的解决方案相应的效果如下图所示,可以看到我们已经可以正确读取刚刚剪切板的内容了。
- 此时,当拿到用户剪切板的内容以后,我们就可以根据某些特点来判断弹窗了。这里我随便使用了一个弹出组件来展示效果:
- 什么?到这里你还是没看懂和网购平台链接之间有什么关系?ok,让我们仔细看一下我分别从两家平台随手复制的两个链接,看出区别了吗?开头的字符串可以很明显看出是各家的名字。
- 那么我只需判断用户剪切板上的字符串是否符合站内的某项规则不就行了吗?让我来举个更具体的栗子,下面链接是我掘金的个人首页,假如用户此时复制了这段文本,然后跳转回我们自己的应用后,刚刚的代码就可以加一个逻辑判断,检测用户剪切板上的链接是否是以
juejin.cn
开头的,如果是则跳转首页;如果不是,那么什么事情也不做。
对应的代码如下: - 那么相对应的效果如下,这也就是为什么复制某宝的链接到某东后没任何反应的原因。某东并不是没读取,而是读取后发现不是自家的就不处理罢了。
:
四*. 思维拓展:粘贴图片自动转链接的实现
- 用过相关写作平台的小伙伴大概对在编辑器中直接接粘贴图片的功能不陌生。如掘金的编辑器,当我复制一个图片以后,直接在编辑器中粘贴即可。掘金会自动将图片转换为一个链接,这样会极大的提高创作者的写作体验。
- 那么现在让我们继续发散思维来思考这个需求如何实现,这里我们先随便创建一个富文本框。
既然是粘贴图片,那么最起码我得知道用户什么时候进行粘贴操作吧?这还不简单,直接监听
paste
事件即可。document.addEventListener("paste",()=>{ console.log("用户粘贴了") })
实现的效果如下:
把之前的
clipboard.readText
替换为clipboard.read
以后你的代码应该是下面这样的:document.addEventListener("paste", () => { if (window.isSecureContext && navigator.clipboard) { const clipboardAPI = navigator.clipboard; //获取 clipboard 对象 setTimeout(() => { clipboardAPI.read().then((result) => { console.log("result", result); }); }, 1000); } else { console.log("不支持 clipboard"); } });
让我们复制一张图片到富文本区域执行粘贴操作后,控制台会打印以下信息:
clipboardItem
是一个数组,里面有很多子clipboardItem
,是数组的原因是因为你可以一下子复制多张图片,不过在这里我们只考虑一张图片的场景。- 这里我们取第一项,然后调用
clipboardItem.getType
方法,这个方法需要传递一个文件类型的参数,这里我们传入粘贴内容对应的类型即可,这里传入image/png
。
在控制台这里可以看到一下输出,就表示我们已经正确拿到图片的blob
的格式数据了。 - 此时我们就只需要把相对应的图片数据传递给后端或者
CDN
服务器,让它们返回一个与之对应的链接即可。在掘金的编辑器中,对应的请求就是get-image-url
这个请求。 - 然后调用
textarea.value + link
把链接补充到文章最后位置即可。
五. 源码
<script lang="ts" setup>
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "visible") {
if (window.isSecureContext && navigator.clipboard) {
const clipboardAPI = navigator.clipboard; //获取 clipboard 对象
setTimeout(() => {
clipboardAPI.read().then((result) => {});
}, 1000);
} else {
console.log("不支持 clipboard");
}
}
});
document.addEventListener("paste", () => {
if (window.isSecureContext && navigator.clipboard) {
const clipboardAPI = navigator.clipboard; //获取 clipboard 对象
setTimeout(() => {
clipboardAPI.read().then((result) => {
result[0].getType("image/png").then((blob) => {
console.log("blob", blob);
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = (e) => {
const img = document.createElement("img");
img.src = e.target?.result;
const wrapper = document.getElementById("han");
wrapper.appendChild(img);
};
});
});
}, 1000);
} else {
console.log("不支持 clipboard");
}
});
</script>
<template>
<div id="han" class="w-full h-full bg-blue">
<textarea class="w-300px h-300px"></textarea>
</div>
</template>
六. 思考 writeText 的用法
有了上面的经验,我相信你已经可以自己理解 clipboard
剩下的两个方法 write
和 writeText
了。你可以思考下面的问题:
为什么在掘金复制文章内容后,剪切板会自动加版权信息呢?。
如果你实现了,不妨在评论区写下你的思路~🌹
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。