document.execCommand('copy')
当我们接到复制需求,技术实现上一般是两种选择:
- 简单一点的就直接document.execCommand('copy')命令;
- 如果项目引入了Clipboard库,那么直接用库函数解决复制。
当你去看clipboard库的底层实现的时候,会发现它也是基于document.execCommand实现的复制功能:
那么问题来了,MDN已经明确说明该API已废弃:
这种情况下,为了考虑代码的向后兼容性,我们当然还是要避免调用此类API,除此之外,还有什么可用来实现复制的操作呢?接下来,就到了BOM对象为我们提供的Cliboard-API登场了!
Cliboard
它拥有四个跟剪贴板交互的方法:
- writeText()
navigator.clipboard.writeText(text)
写入特定字符串到操作系统的剪切板,返回promise,一般业务需要支持的复制交互,也就是复制url链接或者文本内容,这类操作通过writeText就能满足
- readText()
navigator.clipboard.readText()
解析系统剪贴板的文本内容,返回promise
read和write这两个API比较特殊,支持读取/写入任意在剪贴板的数据,从安全性设计考虑,需要校验用户权限
- read()
navigator.clipboard.read(); 如果读取的是非文本资源,会报错
。
- write()
navigator.clipboard.write([ClipboardItem]),ClipboardItem是用于表示Clipboard-API的剪贴项的单个标准接口,一般用于读写,即read和write操作。
new ClipboardItem({
'text/plain': new Blob(['hello world'], { type: 'text/plain' }),
})
clipbard与图片复制
要实现复制图片粘贴到浏览器,需要结合window的paste
事件,当我们监听paste事件时,得到的是ClipboardEvent,它包含一个只读的DataTransfer类型(本身是用来保存拖拽交互过程中的数据)的clipboardData对象,于是我们可以从该对象实例上的items属性获取到数据传输列表,从而获得我们想要的图片数据,通过构造File对象,完成文件上传。
window.addEventListener('paste', function (event) {
event.preventDefault()
/** ClipboardEvent.clipboardData 属性保存了一个 DataTransfer 对象 */
const items = event.clipboardData && event.clipboardData.items
var file = null
if (items && items.length) {
// 检索剪切板items
for (var i = 0; i < items.length; i++) {
if (items[i].type.indexOf('image') !== -1) {
file = items[i].getAsFile() // 返回File对象
break
}
if (items[i].type === 'text/html' || items[i] === 'text/plain') {
navigator.clipboard.readText().then((res) => {
console.log(res, '------------') // 文本节点处理
})
}
}
}
event.preventDefault()
})
至于兼容性,一般浏览器都支持该API了,发文的时候查询的caniuse, 如果要兼容低版本浏览器,则需要做一个兜底方案:
调试代码:
/**
<body>
<div>12345</div>
<input type="text" />
<ul class="copy-list">
<li>复制文本</li>
<li>粘贴文本</li>
<li>复制图片</li>
<li>写入剪贴板数据</li>
</ul>
<ul>
<li>测试</li>
</ul>
<script src="./copy.js"></script>
</body>
*/
const inputEl = document.getElementsByTagName('input')[0]
function readCopyText() {
// Clipboard.readText(): 解析系统剪贴板的文本内容
return new Promise((resolve) => {
navigator.clipboard
.readText()
.then((res) => {
inputEl.setAttribute('value', res)
resolve(res)
})
.catch(() => {
resolve('')
})
})
}
function copyText() {
// Clipboard.writeText(): 写入特定字符串到操作系统的剪切板
return new Promise((resolve) => {
const text = inputEl.value
navigator.clipboard
.writeText(text)
.then((res) => {
resolve(res)
})
.catch(() => resolve(''))
})
}
/** 需要权限校验:提前获取 "Permissions API" 的 "clipboard-write" 权限
* Clipboard.write(): 写入图片等任意的数据到剪贴板
* Clipboard.read(): 读取剪贴板内容,如果不是纯文本,会报错
*/
function authCheck() {
return new Promise((resolve) => {
navigator.permissions
.query({ name: 'clipboard-read' })
.then((result) => {
if (result.state == 'granted' || result.state == 'prompt') {
resolve(true)
} else {
resolve(false)
}
})
.catch(() => {
return false
})
})
}
function copyImg() {
return new Promise(async (resolve) => {
const authResult = await authCheck()
if (authResult) {
// 如果读取的非文本资源,报错:MException: No valid data on clipboard.
const clipboardItems = await navigator.clipboard.read()
for (let i = 0; i < clipboardItems.length; i++) {
const clipboardItem = clipboardItems[i]
const types = clipboardItem.types
for (let j = 0; j < types.length; j++) {
const type = types[j]
if (type === 'text/plain' || type === 'text/html') {
/** 文本节点处理 */
const contentBlob = await clipboardItem.getType(type)
const text = await contentBlob.text()
resolve(text)
return
}
}
}
resolve('')
}
})
}
function setClipboardData() {
return new Promise(async (resolve) => {
const authResult = await authCheck()
if (authResult) {
// ClipboardItem类型
const item = new ClipboardItem({
'text/plain': new Blob(['hello world'], { type: 'text/plain' }),
})
navigator.clipboard.write([item]).then((res) => {
console.log('res', res)
resolve(res)
})
// DataTransfer类型, 仅用于交互式复制对象
// const transferData = new DataTransfer()
// transferData.items.add('text/plain', '替换了数据')
// navigator.clipboard.write(transferData).then((res) => {
// console.log('res', res)
// resolve(res)
// })
}
})
}
const copyFnList = [copyText, readCopyText, copyImg, setClipboardData]
const ulList = document.querySelectorAll('ul.copy-list>li')
for (let i = 0; i < ulList.length; i++) {
ulList[i].addEventListener('click', () => {
const bindFn = copyFnList[i]
bindFn().then((res) => {
console.log(res, '??copyImg??')
})
})
}
window.addEventListener('paste', function (event) {
event.preventDefault()
/** ClipboardEvent.clipboardData 属性保存了一个 DataTransfer 对象 */
const items = event.clipboardData && event.clipboardData.items
var file = null
if (items && items.length) {
// 检索剪切板items
for (var i = 0; i < items.length; i++) {
if (items[i].type.indexOf('image') !== -1) {
file = items[i].getAsFile() // 返回File对象
break
}
if (items[i].type === 'text/html' || items[i] === 'text/plain') {
navigator.clipboard.readText().then((res) => {
console.log(res, '------------') // 文本节点处理
})
}
}
}
event.preventDefault()
})