本文详解JavaScript中实现剪贴板操作的两种主要方式:推荐的异步Clipboard API与已废弃的document.execCommand。重点解析如何结合Promise与ClipboardItem实现文字及图片的复制,并对比两者的浏览器兼容性、安全限制及性能差异,提供基于Vue的完整封装示例。
一. 基于 Clipboard API 复制文字(推荐)
基本概念
Clipboard API 提供了一套在浏览器环境中操作系统剪贴板的 JavaScript 接口,支持开发者读取和写入剪贴板数据,从而完成复制、剪切和粘贴等操作。该接口不仅支持纯文本,还能处理图像及其他二进制数据。它适用于需要增强用户交互的网页场景,例如实现一键复制功能,或在用户执行复制操作时自动附加版权信息等额外数据。
https://developer.mozilla.org/zh-CN/docs/Web/API/Clipboard_API
主要方法
Clipboard API 提供了以下核心方法用于执行剪贴板的读写任务:
- navigator.clipboard.writeText(text):将指定文本写入剪贴板。该方法为异步操作,返回一个 Promise 对象,写入成功时 Promise 被解析,失败时被拒绝。
- navigator.clipboard.readText():从剪贴板读取纯文本内容。同样为异步方法,返回 Promise 对象,解析后包含剪贴板内的文本字符串。
- navigator.clipboard.write(data):用于写入复杂数据类型(如文件、图像等)到剪贴板。参数 data 为包含 ClipboardItem 对象的数组,每个对象代表剪贴板中的一条数据。该方法异步执行,返回 Promise 对象。
- navigator.clipboard.read():从剪贴板读取复杂数据类型(如文件、图像等)。该方法返回 Promise 对象,解析后提供一个包含 ClipboardItem 对象的数组。
使用限制
- 用户授权:出于安全与隐私保护,浏览器在调用 Clipboard API 时通常要求用户明确授权。例如,尝试读取或写入数据时,浏览器可能弹出权限请求对话框。
- 安全上下文:Clipboard API 仅在安全上下文中可用,例如 HTTPS 协议页面或 localhost 本地环境。
- 浏览器兼容性:尽管现代主流浏览器已广泛支持 Clipboard API,但部分旧版本浏览器仍不支持。因此,在实际项目中需考虑兼容性处理方案。
实际应用示例
<template>
<el-button type="primary" @click="handleCopy">复制文本</el-button>
<div>{{ message }}</div>
</template>
<script setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
const message = ref('复制的内容')
const handleCopy = () => {
navigator.clipboard
.writeText(message.value)
.then(() => {
ElMessage({
message: '复制成功',
type: 'success',
})
})
.catch((err) => {
console.error('复制失败:', err)
ElMessage({
message: '复制失败',
type: 'error',
})
})
}
</script>二、基于 document.execCommand('copy')
document.execCommand('copy') 是一种用于在网页中执行复制操作的旧式 Web API,允许通过脚本在无用户交互(如点击或按键)的情况下将文本复制到剪贴板。然而,该 API 在现代 Web 开发中已被标记为过时(deprecated),并在众多现代浏览器中受到严格限制或完全移除支持,特别是在缺乏用户明确交互触发的场景下。
https://developer.mozilla.org/zh-CN/docs/Web/API/Document/execCommand
缺陷
- 仅能操作 input、textarea 或具有 contenteditable 属性的元素。
execCommand是同步操作,若复制/粘贴大量数据,可能导致页面卡顿,影响用户体验。- 它只能复制当前被选中的内容,无法直接向剪贴板写入任意内容。
- 部分浏览器会弹出提示框请求用户许可,在用户做出选择前,页面可能处于无响应状态。
实际应用示例
<template>
<el-button type="primary" @click="handleCopy2">复制文本2</el-button>
<div>{{ message }}</div>
</template>
<script setup>
import {
copyText,
copyImage,
imageUrlToBase64,
parseBase64,
} from './common/copy'
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
const message = ref('复制的内容')
const handleCopy2 = () => {
// 动态创建 textarea 标签
const textarea = document.createElement('textarea')
// 将该 textarea 设为 readonly 防止 iOS 下自动唤起键盘,同时将 textarea 移出可视区域
textarea.readOnly = 'readonly'
textarea.style.position = 'absolute'
textarea.style.left = '-9999px'
textarea.style.opacity = '0'
// 将要 copy 的值赋给 textarea 标签的 value 属性
textarea.value = message.value
// 将 textarea 插入到 body 中
document.body.appendChild(textarea)
// 选中值并复制
textarea.select()
const result = document.execCommand('Copy')
if (result) {
ElMessage({
message: '复制成功',
type: 'success',
})
}
document.body.removeChild(textarea)
}
</script>说明
clipboard.js 底层实现也是基于 document.execCommand 去实现的
function createFakeElement(value) {
var isRTL = document.documentElement.getAttribute('dir') === 'rtl';
var fakeElement = document.createElement('textarea'); // Prevent zooming on iOS
fakeElement.style.fontSize = '12pt'; // Reset box model
fakeElement.style.border = '0';
fakeElement.style.padding = '0';
fakeElement.style.margin = '0'; // Move element out of screen horizontally
fakeElement.style.position = 'absolute';
fakeElement.style[isRTL ? 'right' : 'left'] = '-9999px'; // Move element to the same position vertically
var yPosition = window.pageYOffset || document.documentElement.scrollTop;
fakeElement.style.top = "".concat(yPosition, "px");
fakeElement.setAttribute('readonly', '');
fakeElement.value = value;
return fakeElement;
}
var fakeCopyAction = function fakeCopyAction(value, options) {
var fakeElement = createFakeElement(value);
options.container.appendChild(fakeElement);
var selectedText = select_default()(fakeElement);
command('copy');
fakeElement.remove();
return selectedText;
};三、复制图片功能
<template>
<el-button type="primary" @click="handleCopyImage">复制图片</el-button>
<div>{{ message }}</div>
</template>
<script setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
const message = ref('复制的内容')
const handleCopyImage = async () => {
//具体看下面的封装
await copyImage('https://cn.vitejs.dev/logo-with-shadow.png')
ElMessage({
message: '复制成功',
type: 'success',
})
}
</script>四、封装
/**
* 图片转base64
* @param {string} 图片地址
* @returns
*/
export const imageUrlToBase64 = (imageUrl) => {
return new Promise((resolve, reject) => {
let image = new Image()
image.setAttribute('crossOrigin', 'Anonymous')
image.src = imageUrl
image.onload = function () {
const canvas = document.createElement('canvas')
canvas.width = image.width
canvas.height = image.height
const context = canvas.getContext('2d')
context.drawImage(image, 0, 0, image.width, image.height)
const base64Str = canvas.toDataURL('image/png')
resolve(base64Str)
}
image.onerror = function (e) {
reject(e)
}
})
}
/**
* 转换base64
* @param {string} base64
* @returns
*/
export function parseBase64(base64) {
let re = new RegExp('data:(?<type>.*?);base64,(?<data>.*)')
let res = re.exec(base64)
if (res) {
return {
type: res.groups.type,
ext: res.groups.type.split('/').slice(-1)[0],
data: res.groups.data,
}
}
}
/**
* 复制文字
* @param {string} text 要复制的文本
* @returns {boolean} true/false
*/
export const copyText = async (text) => {
if (navigator && navigator.clipboard) {
await navigator.clipboard.writeText(text)
return true
}
// 动态创建 textarea 标签
const textarea = document.createElement('textarea')
// 将该 textarea 设为 readonly 防止 iOS 下自动唤起键盘,同时将 textarea 移出可视区域
textarea.readOnly = 'readonly'
textarea.style.position = 'absolute'
textarea.style.left = '-9999px'
textarea.style.opacity = '0'
// 将要 copy 的值赋给 textarea 标签的 value 属性
textarea.value = text
// 将 textarea 插入到 body 中
document.body.appendChild(textarea)
// 选中值并复制
textarea.select()
const result = document.execCommand('Copy')
document.body.removeChild(textarea)
return result
}
/**
* 复制图片
* @param {string} imageUrl 图片地址
* @param {boolean} isBase64 是否是base64
*/
export const copyImage = async (imageUrl, isBase64 = false) => {
let base64Url = ''
if (!isBase64) {
base64Url = await imageUrlToBase64(imageUrl)
} else base64Url = imageUrl
const parsedBase64 = parseBase64(base64Url)
let type = parsedBase64.type
//将base64转为Blob类型
let bytes = atob(parsedBase64.data)
let ab = new ArrayBuffer(bytes.length)
let ua = new Uint8Array(ab)
for (let i = 0; i < bytes.length; i++) {
ua[i] = bytes.charCodeAt(i)
}
let blob = new Blob([ab], { type })
navigator.clipboard.write([new ClipboardItem({ [type]: blob })])
}到此这篇关于JS实现复制粘贴文字以及图片的文章就介绍到这了,更多相关js复制粘贴文字内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!