原来 Clipboard 还能复制图像?原理是什么

在写了 这个 29.7 K 的剪贴板 JS 库有点东西! 这篇文章之后,收到了小伙伴提的两个问题:

1.clipboard.js 这个库除了复制文字之外,能复制图像么?

2.clipboard.js  这个库依赖的 document.execCommand API 已被废弃了,以后应该怎么办?

(图片来源:https://developer.mozilla.org/zh-CN/docs/Web/API/Document/execCommand)

接下来,本文将围绕上述两个问题展开,不过在看第一个问题之前,我们先来简单介绍一下 剪贴板 ????。

剪贴板(英语:clipboard),有时也称剪切板、剪贴簿、剪贴本。它是一种软件功能,通常由操作系统提供,作用是使用复制和粘贴操作短期存储数据和在文档或应用程序间转移数据。它是图形用户界面(GUI)环境中最常用的功能之一,通常实现为匿名、临时的数据缓冲区,可以被环境内的大部分或所有程序使用编程接口访问。—— 维基百科

通过以上的描述我们可以知道,剪贴板架起了一座桥梁,使得在各种应用程序之间,传递和共享信息成为可能。然而美中不足的是,剪贴板只能保留一份数据,每当新的数据传入,旧的便会被覆盖。

了解完 剪贴板 ???? 的概念和作用之后,我们马上来看一下第一个问题:clipboard.js 这个库除了复制文字之外,能复制图像么?

一、clipboard.js 能否复制图像?

clipboard.js 是一个用于将 文本 复制到剪贴板的 JS 库。没有使用 Flash,没有使用任何框架,开启 gzipped 压缩后仅仅只有 3kb

(图片来源:https://clipboardjs.com/#example-text)

当你看到 “A modern approach to copy text to clipboard” 这个描述,你是不是已经知道答案了。那么实际的情况是怎样呢?下面我们来动手验证一下。在 这个 29.7 K 的剪贴板 JS 库有点东西! 这篇文章中,阿宝哥介绍了在实例化 ClipboardJS 对象时,可以通过 options 对象的 target 属性来设置复制的目标:

// https://github.com/zenorocha/clipboard.js/blob/master/demo/function-target.html
let clipboard = new ClipboardJS('.btn', {target: function() {return document.querySelector('div');}
});

利用 clipboard.js 的这个特性,我们可以定义以下 HTML 结构:

<div id="container"><img src="http://cdn.semlinker.com/abao.png" width="80" height="80"/><p>大家好,我是阿宝哥</p>
</div>
<button class="btn">复制</button>

然后在实例化 ClipboardJS 对象时设置复制的目标是 #container 元素:

const clipboard = new ClipboardJS(".btn", {target: function () {return document.querySelector("#container");}
});

之后,我们点击页面中的 复制 按钮,对应的效果如下图所示:

观察上图可知,页面中的图像和文本都已经被复制了。对于文本来说,大家应该都很清楚。而对于图像来说,到底复制了什么?我们又该如何获取已复制的内容呢?针对这个问题,我们可以利用 HTMLElement 对象上的 onpaste 属性或者监听元素上的 paste 事件。

这里我们通过设置 document 对象的 onpaste 属性,来打印一下粘贴事件对应的事件对象:

document.onpaste = function (e) {console.dir(e);
}

当我们点击 复制 按钮,然后在页面执行 粘贴 操作后,控制台会打印出以下内容:

通过上图可知,在 ClipboardEvent 对象中含有一个 clipboardData 属性,该属性包含了与剪贴板相关联的数据。详细分析了 clipboardData 属性之后,我们发现已复制的图像和普通文本被封装为 DataTransferItem 对象。

为了更方便地分析 DataTransferItem 对象,阿宝哥重新更新了 document 对象的 onpaste 属性:

在上图中,我们可以清楚的看到 DataTransferItem 对象上含有 kindtype 属性分别用于表示数据项的类型(string 或 file)及数据对应的 MIME 类型。利用 DataTransferItem 对象提供的 getAsString 方法,我们可以获取该对象中保存的数据:

相信看完以上的输出结果,小伙伴们就很清楚第一个问题的答案了。那么如果想要复制图像的话,应该如何实现呢?其实这个问题的答案与小伙伴提的第二个问题的答案是一样的,我们可以利用 Clipboard API 来实现复制图像的问题及解决 document.execCommand API 已被废弃的问题。

接下来,我们的目标就是实现复制图像的功能了,因为要利用到 Clipboard API,所以阿宝哥先来介绍一下该 API。

二、Clipboard API 简介

Clipboard 接口实现了 Clipboard API,如果用户授予了相应的权限,就能提供系统剪贴板的读写访问。在 Web 应用程序中,Clipboard API 可用于实现剪切、复制和粘贴功能。该 API 用于取代通过 document.execCommand API 来实现剪贴板的操作。

在实际项目中,我们不需要手动创建 Clipboard 对象,而是通过 navigator.clipboard 来获取  Clipboard 对象:

在获取 Clipboard 对象之后,我们就可以利用该对象提供的 API 来访问剪贴板,比如:

navigator.clipboard.readText().then(clipText => document.querySelector(".editor").innerText = clipText);

以上代码将 HTML 中含有 .editor 类的第一个元素的内容替换为剪贴板的内容。如果剪贴板为空,或者不包含任何文本,则元素的内容将被清空。这是因为在剪贴板为空或者不包含文本时,readText 方法会返回一个空字符串。

在继续介绍 Clipboard API 之前,我们先来看一下 Navigator API: clipboard 的兼容性:

(图片来源:https://caniuse.com/mdn-api_navigator_clipboard)

异步剪贴板 API 是一个相对较新的 API,浏览器仍在逐渐实现它。由于潜在的安全问题和技术复杂性,大多数浏览器正在逐步集成这个 API。对于浏览器扩展来说,你可以请求 clipboardRead 和 clipboardWrite 权限以使用 clipboard.readText() 和 clipboard.writeText()。

好的,接下来阿宝哥来演示一下如何使用 clipboard 对象提供的 API 来操作剪贴板,以下示例的运行环境是 Chrome 87.0.4280.88

三、将数据写入到剪贴板

3.1 writeText()

writeText 方法可以把指定的字符串写入到系统的剪贴板中,调用该方法后会返回一个 Promise 对象:

<button onclick="copyPageUrl()">拷贝当前页面地址</button>
<script>async function copyPageUrl() {try {await navigator.clipboard.writeText(location.href);console.log("页面地址已经被拷贝到剪贴板中");} catch (err) {console.error("页面地址拷贝失败: ", err);}}
</script>

对于上述代码,当用户点击 拷贝当前页面地址 按钮时,将会把当前的页面地址拷贝到剪贴板中。

3.2 write()

write 方法除了支持文本数据之外,还支持将图像数据写入到剪贴板,调用该方法后会返回一个 Promise 对象。

<button onclick="copyPageUrl()">拷贝当前页面地址</button>
<script>async function copyPageUrl() {const text = new Blob([location.href], {type: 'text/plain'});try {await navigator.clipboard.write(new ClipboardItem({"text/plain": text,}),);console.log("页面地址已经被拷贝到剪贴板中");} catch (err) {console.error("页面地址拷贝失败: ", err);}}
</script>

在以上代码中,我们先通过 Blob API 创建 Blob 对象,然后使用该 Blob 对象来构造 ClipboardItem 对象,最后再通过 write 方法把数据写入到剪贴板。介绍完如何将数据写入到剪贴板,下面我们来介绍如何从剪贴板中读取数据。

对 Blob API 感兴趣的小伙伴,可以阅读 你不知道的 Blob 这篇文章。

四、从剪贴板中读取数据

4.1 readText()

readText 方法用于读取剪贴板中的文本内容,调用该方法后会返回一个 Promise 对象:

<button onclick="getClipboardContents()">读取剪贴板中的文本</button>
<script>async function getClipboardContents() {try {const text = await navigator.clipboard.readText();console.log("已读取剪贴板中的内容:", text);} catch (err) {console.error("读取剪贴板内容失败: ", err);}}
</script>

对于上述代码,当用户点击 读取剪贴板中的文本 按钮时,如果当前剪贴板含有文本内容,则会读取剪贴板中的文本内容。

4.2 read()

read 方法除了支持读取文本数据之外,还支持读取剪贴板中的图像数据,调用该方法后会返回一个 Promise 对象:

<button onclick="getClipboardContents()">读取剪贴板中的内容</button>
<script>
async function getClipboardContents() {try {const clipboardItems = await navigator.clipboard.read();for (const clipboardItem of clipboardItems) {for (const type of clipboardItem.types) {const blob = await clipboardItem.getType(type);console.log("已读取剪贴板中的内容:", await blob.text());}}} catch (err) {console.error("读取剪贴板内容失败: ", err);}}
</script>

对于上述代码,当用户点击 读取剪贴板中的内容 按钮时,则会开始读取剪贴板中的内容。到这里 clipboard 对象中涉及的 4 个 API,阿宝哥都已经介绍完了,最后我们来看一下如何实现复制图像的功能。

五、实现复制图像的功能

在最后的这个示例中,阿宝哥将跟大家一步步实现复制图像的核心功能,除了复制图像之外,还会同时支持复制文本。在看具体代码前,我们先来看一下实际的效果:

在上图对应的网页中,我们先点击 复制 按钮,则图像和文本都会被选中。之后,我们在点击 粘贴 按钮,则控制台会输出从剪贴板中读取的实际内容。在分析具体的实现方式前,我们先来看一下对应的页面结构:

<div id="container"><img src="http://cdn.semlinker.com/abao.png" width="80" height="80" /><p>大家好,我是阿宝哥</p>
</div>
<button onclick="writeDataToClipboard()">复制</button>
<button onclick="readDataFromClipboard()">粘贴</button>

上面的页面结构很简单,下一步我们来逐步分析一下以上功能的实现过程。

5.1 请求剪贴板写权限

默认情况下,会为当前的激活的页面自动授予剪贴板的写入权限。出于安全方面考虑,这里我们还是主动向用户请求剪贴板的写入权限:

async function askWritePermission() {try {const { state } = await navigator.permissions.query({name: "clipboard-write",});return state === "granted";} catch (error) {return false;}
}

5.2 往剪贴板写入图像和普通文本数据

要往剪贴板写入图像数据,我们就需要使用 navigator.clipboard 对象提供的 write 方法。如果要写入图像数据,我们就需要获取该图像对应的 Blob 对象,这里我们可以通过 fetch API 从网络上获取图像对应的响应对象并把它转化成 Blob 对象,具体实现方式如下:

async function createImageBlob(url) {const response = await fetch(url);return await response.blob();
}

而对于普通文本来说,只需要使用前面介绍的 Blob API 就可以把普通文本转换为 Blob 对象:

function createTextBlob(text) {return new Blob([text], { type: "text/plain" });
}

在创建完图像和普通文本对应的 Blob 对象之后,我们就可以利用它们来创建 ClipboardItem 对象,然后再调用 write 方法把这些数据写入到剪贴板中,对应的代码如下所示:

async function writeDataToClipboard() {if (askWritePermission()) {if (navigator.clipboard && navigator.clipboard.write) {const textBlob = createTextBlob("大家好,我是阿宝哥");const imageBlob = await createImageBlob("http://cdn.semlinker.com/abao.png");try {const item = new ClipboardItem({[textBlob.type]: textBlob,[imageBlob.type]: imageBlob,});select(document.querySelector("#container"));await navigator.clipboard.write([item]);console.log("文本和图像复制成功");} catch (error) {console.error("文本和图像复制失败", error);}}}
}

在以上代码中,使用了一个 select 方法,该方法用于实现选择的效果,对应的代码如下所示:

function select(element) {const selection = window.getSelection();const range = document.createRange();range.selectNodeContents(element);selection.removeAllRanges();selection.addRange(range);
}

通过 writeDataToClipboard 方法,我们已经把图像和普通文本数据写入剪贴板了。下面我们来使用 navigator.clipboard 对象提供的 read 方法,来读取已写入的数据。如果你需要读取剪贴板的数据,则需要向用户请求 clipboard-read 权限。

5.3 请求剪贴板读取权限

这里我们定义了一个 askReadPermission 函数来向用户请求剪贴板读取权限:

async function askReadPermission() {try {const { state } = await navigator.permissions.query({name: "clipboard-read",});return state === "granted";} catch (error) {return false;}
}

当调用 askReadPermission 方法后,将会向当前用户请求剪贴板读取权限,对应的效果如下图所示:

5.4 读取剪贴板中已写入的数据

创建好 askReadPermission 函数,我们就可以利用之前介绍的 navigator.clipboard.read 方法来读取剪贴板的数据了:

async function readDataFromClipboard() {if (askReadPermission()) {if (navigator.clipboard && navigator.clipboard.read) {try {const clipboardItems = await navigator.clipboard.read();for (const clipboardItem of clipboardItems) {console.dir(clipboardItem);for (const type of clipboardItem.types) {const blob = await clipboardItem.getType(type);console.log("已读取剪贴板中的内容:", await blob.text());}}} catch (err) {console.error("读取剪贴板内容失败: ", err);}}}
}

其实,除了点击 粘贴 按钮之外,我们还可以通过监听 paste 事件来读取剪贴板中的数据。需要注意的是,如果当前的浏览器不支持异步 Clipboard API,我们可以通过 clipboardData.getData 方法来读取剪贴板中的文本数据:

document.addEventListener('paste', async (e) => {e.preventDefault();let text;if (navigator.clipboard) {text = await navigator.clipboard.readText();} else {text = e.clipboardData.getData('text/plain');}console.log('已获取的文本数据: ', text);
});

而对于图像数据,则可以通过以下方式进行读取:

const IMAGE_MIME_REGEX = /^image\/(p?jpeg|gif|png)$/i;document.addEventListener("paste", async (e) => {e.preventDefault();if (navigator.clipboard) {let clipboardItems = await navigator.clipboard.read();for (const clipboardItem of clipboardItems) {for (const type of clipboardItem.types) {if (IMAGE_MIME_REGEX.test(type)) {const blob = await clipboardItem.getType(type);loadImage(blob);return;}}}} else {const items = e.clipboardData.items;for (let i = 0; i < items.length; i++) {if (IMAGE_MIME_REGEX.test(items[i].type)) {loadImage(items[i].getAsFile());return;}}}
});

以上代码中的 loadImage 方法用于实现把复制的图片插入到当前选区已选择的区域中,对应的代码如下:

function loadImage(file) {const reader = new FileReader();reader.onload = function (e) {let img = document.createElement("img");img.src = e.target.result;let range = window.getSelection().getRangeAt(0);range.deleteContents();range.insertNode(img);};reader.readAsDataURL(file);
}

在前面代码中,我们监听了 document 对象的 paste 事件。除了该事件之外,与剪贴板相关的常见事件还有 copycut 事件。篇幅有限,阿宝哥就不继续展开介绍了,感兴趣的小伙伴可以自行阅读相关资料。

好的,至此本文就已经结束了,希望阅读完本文之后,大家对异步的 Clipboard API 会有些了解,有写得不清楚的地方,欢迎你随时跟阿宝哥交流哟。

六、参考资源

  • 维基百科 - 剪贴板

  • MDN - Clipboard

  • MDN - execCommand

  • Web.dev - async-clipboard

推荐阅读

若川知乎高赞:有哪些必看的 JS库?
我在阿里招前端,我该怎么帮你?(现在还可以加模拟面试群)
如何拿下阿里巴巴 P6 的前端 Offer
如何准备阿里P6/P7前端面试--项目经历准备篇
大厂面试官常问的亮点,该如何做出?
如何从初级到专家(P4-P7)打破成长瓶颈和有效突破
若川知乎问答:2年前端经验,做的项目没什么技术含量,怎么办?

末尾

你好,我是若川,江湖人称菜如若川,历时一年只写了一个学习源码整体架构系列~(点击蓝字了解我)

  1. 关注若川视野,回复"pdf" 领取优质前端书籍pdf,回复"1",可加群长期交流学习

  2. 我的博客地址:https://lxchuan12.gitee.io 欢迎收藏

  3. 觉得文章不错,可以点个在看呀^_^另外欢迎留言交流~

精选前端好文,伴你不断成长

若川原创文章精选!可点击

小提醒:若川视野公众号面试、源码等文章合集在菜单栏中间【源码精选】按钮,欢迎点击阅读,也可以星标我的公众号,便于查找

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/276241.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JavaScript 元编程

大家好&#xff0c;我是若川。今天给分享一篇来自freecodecamp的好文。我是freecodecamp杭州社区组织者之一&#xff0c;有一群小伙伴一起组织线下分享活动&#xff0c;不过2020年我们杭州社区几乎没有活跃&#xff0c;我也没有什么贡献。另外&#xff0c;我的公众号「若川视野…

手写一个合格的前端脚手架

为什么我们需要一套脚手架为什么我们需要一套脚手架&#xff0c;它能帮助我们解决哪些痛点问题。•前端项目配置越来越繁琐、耗时&#xff0c;重复无意义的工作•项目结构不统一、不规范•前端项目类型繁多&#xff0c;不同项目不同配置&#xff0c;管理成本高•脚手架也可以是…

第一篇cnblog!

本人才疏学浅&#xff0c;终于通过了cnblog的审核&#xff0c;兴奋之余&#xff0c;发表感言——不容易啊&#xff01;在我的博闻里面&#xff0c;随笔类当然就是技术类的比较多的&#xff0c;特别是实例类的。理论类的大部分放在文章板块&#xff0c;本人e文特别好(哈哈&#…

利用JMeter进行压力测试(1)(转)

转自&#xff1a;http://www.cnblogs.com/game-over/archive/2010/01/08/1642685.html压力测试以软件响应速度为测试目标&#xff0c;尤其是在较短时间内大量并发用户的同时访问时&#xff0c;软件的性能和抗压能力。 JMeter是一款开源的压力测试工具&#xff0c;目前最新Rele…

Git 内部原理图解——对象、分支以及如何从零开始建仓库

我们中的许多人每天都在使用 git&#xff0c;但是有多少人知道它的内部是怎么运作的呢&#xff1f;例如我们使用 git commit 时发生了什么&#xff1f;提交&#xff08;commit&#xff09;与提交之间保存的是什么&#xff1f;两次提交之间难道只是文件的差异&#xff08;dif…

Google, 请不要离开我们!

虽然我是.net阵营, 力挺Silverlight, 但是我真心希望谷歌留在中国, 如果她能够靠谈判求的言论自由的权利, 那将对中国的拥有自由信仰的一族产生重大的影响. 谷歌离开了中国, 不是她想抛弃中国市场, 而是中国决策者背叛了人性. 在此留下 Google 2010年1月14日的logo, 智慧的幽默…

28岁自学3年前端成功转行的励志故事

为什么转行因为混得不好。在成为程序员之前&#xff0c;我干过很多工作。由于学历的问题&#xff08;高中&#xff09;&#xff0c;我的工作基本上都是体力活。包括但不限于&#xff1a;工厂普工、销售&#xff08;没有干销售的才能&#xff09;、搬运工、摆地摊等&#xff0c;…

usb 驱动

usb 驱动学习总结&#xff1a; usb 采用分层的拓扑结构&#xff0c;金字塔型&#xff0c;最多是7层。usb 是主从结构&#xff0c;主和主或者从和从之间不能交换数据。理论上一个usb主控制器最多可接127个设备&#xff0c;协议规定每个usb设备具有一个7bit的地址&#xff0c;范围…

面试字节跳动后的2点总结,建议收藏!

首先我来辟个谣&#xff1a;随便打开一个招聘网站&#xff0c;你会发现前端工程师的岗位需求依旧庞大&#xff0c;大厂人才奇缺&#xff0c;就业薪资起点高&#xff0c;无行业限制。&#xff08;数据来源&#xff1a;职友集&#xff09;前端开发的行业大环境行业升级&#xff0…

phpexcel中文教程-设置表格字体颜色背景样式、数据格式、对齐方式、添加图片、批注、文字块、合并拆分单元格、单元格密码保护

转载连接&#xff1a;http://www.cnblogs.com/huangcong/p/3687665.html phpexcel中文教程-设置表格字体颜色背景样式、数据格式、对齐方式、添加图片、批注、文字块、合并拆分单元格、单元格密码保护 首先到phpexcel官网上下载最新的phpexcel类&#xff0c;下周解压缩一个cla…

2021年的今天,如何成为一名专业的前端工程师?

大家好&#xff0c;我是若川。今天给分享一篇来自阿里克军大佬的好文。以下是正文~如果你想成为一名专业的前端工程师&#xff0c;那么你需要了解要学什么&#xff0c;学到什么程度&#xff0c;以及如何有效地学习。大学里没有正规的前端技术课程&#xff0c;普遍缺少比较权威的…

nc65右键生成菜单_DbSchema生成表单和报表,原来如此简单

DbSchema 8 for Mac是mac上一款非常实用的商业数据库ER图绘制软件&#xff0c;可以轻松的对文档进行注释或标注&#xff0c;而且Dbschema集成了SQL和数据工具&#xff0c;能生成直观的图表、PDF文件或HTML 5文档等&#xff0c;非常的实用。现在就来给大家分享DbSchema如何生成表…

若川的2020年度总结,水波不兴

前言从2014年开始&#xff0c;每一年都会写年度总结&#xff0c;坚持了6个年头。回顾2014&#xff0c;约定2015&#xff08;QQ空间日志&#xff09;2015年总结&#xff0c;淡化旧标签&#xff0c;无惧未来&#xff08;QQ空间日志&#xff09;2016年度总结&#xff0c;毕业工作2…

年度总结文章的抽奖结果公布

大家好&#xff0c;我是若川。2月4日&#xff0c;发表了我的2020年度总结文章《若川的2020年度总结&#xff0c;水波不兴》&#xff0c;本以为阅读量应该突破一千会比较快&#xff0c;实际上比较艰难&#xff0c;而且还掉粉10来人。2020年运营公众号以来&#xff0c;不知不觉发…

Elon Musk

人物事件 成长学习 1971年6月28日&#xff0c;埃隆马斯克在南非的比勒陀利亚出生&#xff0c;他的 埃隆马斯克 父亲是一名南非机电工程师&#xff0c;母亲是加拿大人&#xff0c;从事营养师兼模特。[8] 1981年&#xff0c;10岁的马斯克就拥有了自己的第一台电脑&#xff0c;并…

真诚推荐这7个大佬的公众号,碎片化学习

逆水行舟&#xff0c;不进则退。我们的工作已经占用了大块的时间了&#xff0c;剩下的只有各种碎片&#xff0c;最适合碎片时间学习的&#xff0c;莫过于优质的技术干货公众号啦~以下这些是小编精选&#xff0c;里面有很多资讯和资源&#xff0c;内含干货&#xff0c;希望能给大…

[转]Windows 7 产品密钥是否安全

提到Windows 7&#xff08;或Windows Server 2008&#xff09;有些人认为自己的产品密钥&#xff08;Product Key&#xff09;很安全&#xff0c;甚至在公司内部有些网管也认为公司部署的Windows 7 系统的密钥不会泄露。但其实并非如此&#xff0c;众所周知我们的密钥都是写在注…

HttpWatch的Result中出现Aborted的原因分析[配图]

转载链接&#xff1a;http://www.cnblogs.com/yutiansanshou/archive/2013/02/01/2889486.html 我们在使用HttpWatch进行Web调试的过程中有时候会看到非HTTP Status Code&#xff08;状态码&#xff09;的值&#xff0c; 例如&#xff1a;(Aborted)。 (Aborted)是HttpWatch中定…

《大规模分布式系统架构与设计实战》

《大规模分布式系统架构与设计实战》 基本信息 作者&#xff1a; 彭渊 丛书名&#xff1a; 大数据技术丛书 出版社&#xff1a;机械工业出版社 ISBN&#xff1a;9787111455035 上架时间&#xff1a;2014-2-21 出版日期&#xff1a;2014 年2月 开本&#xff1a;16开 页码&…

WINDOWS下的squid

今天写这篇教程目的在于分享自己在WINDOWS主机下配置squid的方法。哪些地方写的不完善或是不完整或是需要修改的地方&#xff0c;大家可以提出。我会第一时间纠正。下面看正文部分。先提条件&#xff0c;您预安装配置squid的这台计算机必须是联入网络的&#xff0c;系统版本是w…