仅供参考
一、引言
看到这个图标,大家应该都很熟悉,往往放在某行文本旁边,点击即可复制文本内容,这篇文章将仔细说明如何实现“复制
”的功能
上面说到是点击之后实现了复制,那么自然而然地可以想到使用 el-button 按钮,里面装着这个图标,点击图标实际上是点击按钮,触发 click 事件,也就是执行了某个函数,实现了复制,可以看出复制的本质是某个函数的执行
二、方法一:创建隐藏DOM元素
<template>
<div class="mt-96 flex justify-center gap-4 items-center">
<p>{{ content }}</p>
<ElButton type="primary" @click="copy(content)">复制按钮</ElButton>
</div>
</template>
<script setup>
import { ref } from "vue";
import { ElMessage } from "element-plus";
const content = ref("要复制的内容");
const copy = (text) => {
return new Promise((resolve, reject) => {
try {
const input = document.createElement("textarea");
input.setAttribute("readonly", "readonly");
input.value = text;
document.body.appendChild(input);
input.select();
if (document.execCommand("copy")) document.execCommand("copy");
document.body.removeChild(input);
ElMessage.success("复制成功");
resolve(text);
} catch (error) {
ElMessage.error("复制失败");
reject(error);
}
});
};
</script>
实现的效果:
点击复制按钮后,弹出成功提示框,粘贴出的内容就是复制的文本
代码解析:
1、为什么要返回一个Promise对象:目的是将其封装为异步操作,复制成功时通过 resolve(text) 返回结果,发生错误时通过 reject(error) 抛出异常;如果复制后还有后续操作,可以通过 .then(text => {})、.catch(error => {})做进一步处理
2、document.createElement(“textarea”) 创建一个文本框
(可以理解为输入框,type 为 textarea),但这只是创建,还没有添加到页面中,所以无法看到
3、input 是 DOM 对象,也具有方法
;setAttribute(“readonly”, “readonly”) 方法使其只读
,避免用户编辑
4、input.value = text 将需要复制的内容赋值给文本框
5、document.body.appendChild(input) 将指定的 DOM 元素(这里是 input 元素)添加到当前文档的 <body> 标签内部,作为它的最后一个子元素
6、input.select() 选中文本框中的所有内容
7、document.execCommand(“copy”) 调用浏览器的复制命令
,将选中的内容
复制到剪贴板;这一步才真正实现了复制
;为什么需要做一个判断呢?document.execCommand 函数的返回值为布尔类型,true 表示复制成功,false 表示复制失败,但是早期部分浏览器即使复制成功也会返回 false,所以判断的目的是为了兼容
8、document.body.removeChild(input) 移除临时创建的文本框
为什么document.body.appendChild将元素添加到了页面中,但是点击复制的时候却没有看到?
因为后面通过document.body.removeChild将元素移除了,这一过程很快,所以我们看不到有元素显示
这个方法很好理解,但是并不推荐
三、方法二:使用现代浏览器的 Clipboard API
<template>
<div style="display: flex; gap: 10px; align-items: center;">
<p>{{ content }}</p >
<ElButton @click="copy(content)">点击复制</ElButton>
</div>
</template>
<!-- <script setup>
import { ElButton, ElMessage } from 'element-plus';
import {ref} from 'vue'
const content = ref('要复制的内容')
const copy = async(text) => {
try {
// 写入文本到剪贴板
await navigator.clipboard.writeText(text);
ElMessage.success('文本复制成功')
} catch (error) {
ElMessage.error('复制失败')
}
}
</script> -->
什么是
Clipboard API
?
Clipboard API 是一组用于访问和操作系统剪贴板
的 Web API,允许网页读取和写入剪贴板内容(如文本、图像等),提供了比传统 document.execCommand() 方法更现代、更安全的剪贴板操作方式
1、写入内容到剪贴板:通过writeText()
写入文本,write()
写入更复杂的数据(如带格式文本、图像等)。
2、从剪贴板读取内容:通过readText()
读取文本,read()
读取多种格式的内容
3、所有方法均返回 Promise,需配合 async/await 或 .then() 使用
四、封装成工具
从上面的代码可以看出,每个函数都只是对接收的参数做操作,并没有用到组件中其他的内容,因此可以统一写到一个 JS 文件中,封装成工具,暴露出去,想要使用时通过 import 引入即可,避免在多个组件中都写入相同的代码
1、在src 文件夹下创建 utils 文件夹,专门用于存放工具
2、在 utils 文件夹中创建 copy.js 文件,写入函数
export function copyToClip(text) {
return new Promise((resolve, reject) => {
try {
const input = document.createElement("textarea");
input.setAttribute("readonly", "readonly");
input.value = text;
document.body.appendChild(input);
input.select();
if (document.execCommand("copy")) document.execCommand("copy");
document.body.removeChild(input);
resolve(text);
} catch (error) {
reject(error);
}
});
}
必须通过 export 将函数暴露出去,否则其他组件无法拿到
不要在工具函数中使用 ElMessage,在组件中调用工具函数后再通过 async/await 后续操作调用 ElMessage
3、在组件中引入并使用工具
<template>
<div style="margin-top: 400px; display: flex; gap: 10px; align-items: center; justify-content: center;">
<p>{{ content }}</p>
<ElButton type="primary" @click="copyToClip(content)">点击复制</ElButton>
</div>
</template>
<script setup>
import {ref} from 'vue'
import { copyToClip } from './utils/copy';
const content = ref('要复制的内容')
</script>
可以明显看出,不用在每个组件中重复的写函数,通过引入函数名进行调用即可,大大减少代码量
五、自定义指令
封装成工具后,每次使用都要 import 还是有点麻烦,改造成自定义指令
会更加简洁
首先,自定义指令是什么?
1、Vue 本身提供了 v-model、v-show 等内置指令,是固定的写法;而自定义指令可以随便更改,比如我要使用一个复制的自定义指令,可以是 v-copy,也可以是 v-copyText,由自己设定
2、自定义指令允许将重复的 DOM 操作(如样式修改、事件绑定、动画效果等)封装为可复用的模块,避免在组件中重复编写代码。可以简单的理解为,自定义指令的功能就是函数的功能
书写复制的自定义指令:
import { copyToClip } from '@/utils/copy'
const handler = (el: any, binding: any) => {
el._copyText = binding.value
if (!el._copyHandler) {
el._copyHandler = function (event: Event) {
event.stopPropagation() // 阻止事件冒泡到父级
copyToClip(this._copyText).then(() => {
ElMessage.success(window.$t('common.copied'))
})
}
}
el.removeEventListener('click', el._copyHandler)
el.addEventListener('click', el._copyHandler)
}
export default {
mounted: handler,
updated: handler,
unmounted: (el: any) => {
el.removeEventListener('click', el._copyHandler)
}
}
代码解析:
1、函数 handler 中的两个形参,el:给哪个 DOM 元素添加了该自定义指令,那么 el 就是该 DOM 对象;binding:DOM 元素的绑定值,在复制的自定义指令中,binding 得到的就是将要复制的内容
2、el._copyText = binding.value 将被复制的内容赋值给 _copyText
3、el.addEventListener(‘click’, el._copyHandler) 为 DOM 元素添加点击事件,点击事件触发,自动调用 el._copyHandler 函数
4、为什么要对 el._copyHandler 做一个判断:避免重复创建函数;如果没有这个判断,每次 handler 执行都会创建一个 el._copyHandler
5、为什么先移除事件监听,又添加事件监听?因为 handler 可能执行多次,避免添加重复的点击事件
6、export default 导出的是一个对象,mounted、updated 分别是元素挂载到 DOM 后、组件更新后触发 handler 函数
完成这步之后,还不能直接使用自定义指令,需要在 main.js 中全局注册自定义指令
import copy from 从上面的导出
app.directive('copy', copy)
前一个 copy 是指令的名字,后一个 copy 是上面 export default 导出的对象
此时,就可以使用自定义指令来完成复制操作了:
<ElButton v-copy="要复制的内容"></ElButton>
该自定义指令的本质是元素挂载后、更新后给元素添加点击事件