# Clipboard操作
剪切板,有时候也叫剪贴板,一个意思。浏览器允许js脚本读写剪切板, 自动复制或者粘贴内容,比如在实现一些一键复制时, 点击复制按钮, 就可以指定内容复制进剪切板。
实现剪切板的操作有这么几种
- Document.execCommand() 方法
- 异步的 Clipboard Api
- copy事件和paste事件
- 比较流行的三方js库 clipboard.js (opens new window)
# 1、Document.execCommand()
这是操作剪切板的传统方法,各种浏览器都支持
它支持复制、剪切、粘贴这三个操作
document.execCommand('copy')
document.execCommand('cut')
document.execCommand('paste')
<body>
<input type="text" id="text_input" value="1234567890" />
<input type="text">
<button onclick="handleCopy()">copy</button>
<script type="text/javascript">
const input = document.querySelector('#text_input');
function handleCopy() {
input.select();
document.execCommand('copy');
}
</script>
</body>
复制操作最好放在由用户手动触发的回调里面,不然可能会失败。
function handlePaste() {
text_output.focus();
document.execCommand('paste');
}
但是这个paste操作在比如Chrome这类浏览器中时不允许调用触发的。
# 缺点
Document.execCommand()
方法虽然方便,但是有一些缺点。
首先,它只能将选中的内容复制到剪贴板,无法向剪贴板任意写入内容。
其次,它是同步操作,如果复制/粘贴大量数据,页面会出现卡顿。有些浏览器还会跳出提示框,要求用户许可,这时在用户做出选择前,页面会失去响应。
为了解决这些问题,浏览器厂商提出了异步的 Clipboard API。
还有就是上面提到的, 某些浏览器不允许 paste
调用。
# 2、异步ClipboardAPI
异步ClipboardAPI是下一代的剪切板操作方法 比传统的 execCommand()
方法更强大、更合理。它的所有操作都是异步的, 返回Promise对象, 不会造成页面卡顿。 而且, 他可以将任何内容(比如图片)放入剪切板
navigator.clipboard
只读属性返回Clipboard对象, 所有的操作都通过这个对象进行。
const clipboard = navigator.clipboard
如果返回undefined, 那么说明当前浏览器不支持这个API。
由于用户可能把敏感数据(比如密码)放在剪切板, 允许脚本任意读取会产生安全风险, 所以这个API的安全限制比较多。
首先,Chrome 浏览器规定,只有 HTTPS 协议的页面才能使用这个 API。不过,开发环境(localhost
)允许使用非加密协议。
其次, 调用时需要明确获得用户的许可。 权限的具体实现使用了Permissions API, 和剪切板相关的有两个权限, clipboard-read
,clipboard-write
。写权限(复制内容到剪切板)自动授予脚本, 而读权限(读取剪切板内容)必须用户明确同意给予。
# 3、Clipboard对象
Clipboard对象提供了4个方法, 用来读写剪切板。 他们都是异步方法, 返回Promise对象。
# 3.1 Clipboard.readText()
Clipboard.readText()
方法用于复制剪切板里面的文本数据。
<button onclick="readTextFromClipboard()">read text from clipboard</button>
<script>
function readTextFromClipboard(e) {
try {
navigator.clipboard.readText().then(res => {
const p = document.createElement('p');
p.textContent = res;
document.body.appendChild(p);
});
} catch (error) {
console.error(`failed to read content from clipboard, ${error}`);
}
}
</script>
上面代码中, 当用户点击按钮之后, 就会输出剪切板里面的文本。如果没有授权过, 浏览器还会弹出这样子一个对话框,
询问用户是否同意脚本读取剪切板。如果用户不同意, 那么就会走入到catch里面, 捕获错误。
# 3.2 Clipboard.read()
Clipboard.read()
方法用于复制剪切板里面的数据, 可以是文本数据, 也可以是二进制数据(比如图片), 这个方法也需要用户明确给予许可。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>read.html</title>
</head>
<body>
<button onclick="getClipboardContents()">read</button>
<script>
async function getClipboardContents() {
try {
const clipboardItems = await navigator.clipboard.read();
console.log(clipboardItems);
for (const item of clipboardItems) {
for (const type of item.types) {
const blob = await item.getType(type);
readBlobData(blob, type);
console.log(blob);
// console.log(URL.createObjectURL(blob));
}
}
} catch (error) {
console.error(error);
}
}
function readBlobData(blob, type) {
let reader = new FileReader();
reader.onloadend = function () {
console.log(reader.result);
const p = document.createElement('p');
switch (type) {
case 'text/plain':
p.textContent = '读取结果: ' + reader.result;
break;
case 'text/html':
p.innerHTML = reader.result;
break;
case 'image/png':
const img = new Image();
img.src = reader.result;
p.appendChild(img);
break;
default:
break;
}
document.body.appendChild(p);
reader.abort();
reader = null;
};
if (type === 'image/png') {
reader.readAsDataURL(blob);
} else {
reader.readAsText(blob, 'UTF-8');
}
}
</script>
</body>
</html>
Clipboard.read()
返回一个Promise<ClipboardItems>
, ClipboardItems
是包含ClipboardItem
的一个数组集合,结构如下图
ClipboardItem.types
是剪切板里面的成员可用的MIME类型, 比如上面的内容, 可以时候纯文本粘贴, 也可以使用HTML格式粘贴, 所以有两个MIME类型, text/plain
和text.html
。
ClipboardItem.getType(type)
用于读取剪切板的具体数据, 参数是对应的MIME类型, 返回该类型对应的数据, 数据格式一般是Blob。
我们可以看一下具体的效果, 我们随便复制一段代码
上面的就是纯文本的内容, 下面的就是html格式的内容(通过innerHTML插入的,他的内容就是带内联样式的html)。
我们借助 FileReader
读取到了Blob的内容, 具体只是可以查看我的另外一个文章Blob。🎉 😄
# 3.3 Clipboard.writeText()
Clipboard.writeText()
用于将内容写入剪切板。
function writeTextToClipboard(str) {
try {
navigator.clipboard.writeText('hello, world!');
} catch (error) {
console.error(error);
}
}
然后就可以粘贴了
# 3.4 Clipboard.write()
Clipboard.write()
方法用于将任意数据写入剪贴板,可以是文本数据,也可以是二进制数据。
该方法接受一个 ClipboardItem 实例作为参数,表示写入剪贴板的数据。建议查看示例代码 (opens new window)
async function handleWriteImageAndText() {
try {
const imgURL = 'https://dummyimage.com/300.png';
const data = await fetch(imgURL);
const blob = await data.blob();
const textBlob = new Blob(['hello, world'], {
type: 'text/plain'
});
const htmlBlob = new Blob(['<p style="color: red;">hello
type: 'text/html'
});
await navigator.clipboard.write([
new ClipboardItem({
[blob.type]: blob,
[textBlob.type]: textBlob,
[htmlBlob.type]: htmlBlob,
})
]);
console.log('Image copied.');
} catch (err) {
console.error(err.name, err.message);
}
}
async function handleWriteText() {
try {
if (!input_for_copy.value) return;
const blob = new Blob([input_for_copy.value], { type: 'text/plain' });
await navigator.clipboard.write([
new ClipboardItem({
[blob.type]: blob
})
]);
write_text.textContent = write_text.textContent + ' copy success!';
} catch (err) {
console.error(err.name, err.message);
}
}
WARNING
注意,Chrome 浏览器目前只支持写入 PNG 格式的图片。
ClipboardItem()
是浏览器原生提供的构造函数,用来生成ClipboardItem
实例,它接受一个对象作为参数,该对象的键名是数据的 MIME 类型,键值就是数据本身。我们可以往一个 ClipboardItem
实例添加多种格式的内容, 提供给不同的长河
# 4、copy, cut事件
当用户向剪切板复制数据时, 将触发 copy 事件。
<div class="source">hello world</div>
<script>
const source = document.querySelector('.source');
// 选中source的文字然后按 ctrl + c 复制, 将触发copy事件
// 这里是将复制的内容转为大写
source.addEventListener('copy', e => {
console.log(e);
const selection = window.getSelection();
e.clipboardData.setData(
'text/plain',
selection.toString().toUpperCase()
);
e.preventDefault();
});
</script>
示例中, 事件对象的 clipboardData
属性包含了剪切板数据,还有其他的一些比较实用的属性和方法。
Event.clipboardData.setData(type, data)
:修改剪贴板数据,需要指定数据类型。Event.clipboardData.getData(type)
:获取剪贴板数据,需要指定数据类型。Event.clipboardData.clearData([type])
:清除剪贴板数据,可以指定数据类型。如果不指定类型,将清除所有类型的数据。Event.clipboardData.items
:一个类似数组的对象,包含了所有剪贴项,不过通常只有一个剪贴项。
再来一个拦截用户复制操作的的示例
document.addEventListener('copy', e => {
e.preventDefault();
// navigator.clipboard.writeText('hello, world!');
const textBlob = new Blob(['嘿嘿'], {
type: 'text/plain'
});
navigator.clipboard.write([
new ClipboardItem({
[textBlob.type]: textBlob
})
]);
});
当用户复制时,preventDefault
会阻止复制的默认操作,然后写入我们自定义的内容到剪切板。
cut
事件则是在用户进行剪切操作时触发,它的处理跟copy
事件完全一样,也是从Event.clipboardData
属性拿到剪切的数据。
# 5、paste 事件
当用户ctrl + v 粘贴的时候会触发的事件,
举一个实用的例子, 比如不允许密码框复制输入密码
<body>
<p>asdasdasdasd</p>
<input type="text" />
<input type="text" id="password" />
<script>
const psd = document.querySelector('#password');
psd.addEventListener('paste', e => {
e.preventDefault();
console.log(e);
navigator.clipboard.readText().then(res => {
console.log(res);
});
});
psd.addEventListener('copy', e => {
console.log(e);
e.preventDefault();
e.stopPropagation();
});
</script>
</body>
e.preventDefault();
会阻止复制输入密码,当然也可以防止密码被复制。
# 6、clipboard.js
npm i clipboard -S
大致看了眼源码,用的也是Document.execCommand()
这个解决方案。需要借助一个DOM 节点来实现。
用法不难, 看文档即可, 最简单实用的可能就是这个方法
new ClipboardJS('#dynamic_btn', {
text: function (trigger) {
console.log(trigger);
return 'sss';
}
});
如果需要动态设置, 还是给这个元素设置自定义属性, 然后读取这个属性, 模拟click即可。
# 7、总结
如果对浏览器版本适配要求不高, 还是使用 navigator.clipboard
这个原生API比较实用。 当然clipboard.js
这个三方库的star数也不少, 用的人很多。