js复制到剪切板
应用场景
在前端开发中经常遇到需要复制内容到剪切板的场景,比如点击一个按钮,把某一条数据的url字段写入剪切板。
那对于这个需求,总结用两种方式:
1、await navigator.clipboard.writeText('xxx')
通过clipboard API写入剪切板(api受限浏览器版本,能支持拷贝图片,未来越多用这个)
2、document.execCommand('copy')
通过document.execCommand(‘copy’)写入剪切板 (兼容性好,缺点不能拷贝图片,拷贝内容比较多,会卡一下)
clipboard API
Clipboard API 是下一代的剪贴板操作方法,比传统的document.execCommand()方法更强大、更合理;
它的所有操作都是异步的,返回 Promise 对象,不会造成页面卡顿。
而且,它可以将任意内容(比如图片)放入剪贴板;
navigator.clipboard属性返回 Clipboard 对象,所有操作都通过这个对象进行;
read()
从剪贴板读取数据(比如图片),返回一个 Promise 对象。When the data has been retrieved, the promise is resolved with a DataTransfer object that provides the data。
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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(URL.createObjectURL(blob)); } } } catch (err) { console.error(err.name, err.message); } }
|
readText()
从操作系统读取文本;returns a Promise which is resolved with a DOMString containing the clipboard’s text once it’s available。
1 2 3 4 5 6 7 8
| document.body.addEventListener( 'click', async (e) => { const text = await navigator.clipboard.readText(); console.log(text); } )
|
write()
写入任意数据至操作系统剪贴板。This asynchronous operation signals that it’s finished by resolving the returned Promise。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| try { const imgURL = '/images/photo-1676345338852-29fb1026c12f.webp'; const data = await fetch(imgURL); const blob = await data.blob(); await navigator.clipboard.write([ new ClipboardItem({ [blob.type]: blob }) ]); console.log('Image copied.'); } catch (err) { console.error(err.name, err.message); }
|
write写入图片的时候,可能会出现,格式等不正确的情况,可以通过将图片转成base64的方式,再转成blob的方式,再写入剪切板。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| function imageBase64(img) { var canvas = document.createElement("canvas"); canvas.width = img.width; canvas.height = img.height; var ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0, img.width, img.height); var dataURL = canvas.toDataURL("image/png"); return dataURL; }
function base64ToBlob(b64Data, contentType, sliceSize) { contentType = contentType || ''; sliceSize = sliceSize || 512; var byteCharacters = window.atob(b64Data); var byteArrays = []; for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) { var slice = byteCharacters.slice(offset, offset + sliceSize); var byteNumbers = new Array(slice.length); for (var i = 0; i < slice.length; i++) { byteNumbers[i] = slice.charCodeAt(i); } var byteArray = new Uint8Array(byteNumbers); byteArrays.push(byteArray); } console.log(byteArrays) var blob = new Blob(byteArrays, { type: contentType }); return blob; }
copy_img.onclick = async _ => { let base64 = imageBase64(img) let blob = base64ToBlob(base64.replace('data:image/png;base64,', ''), 'image/png') clipboardObj.write([ new ClipboardItem({ 'image/png': blob }) ]) }
|
最终写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| imageBase64(img) { var canvas = document.createElement("canvas"); canvas.width = img.width; canvas.height = img.height; var ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0, img.width, img.height); var dataURL = canvas.toDataURL("image/png"); return dataURL; },
base64ToBlob(b64Data, contentType, sliceSize) { contentType = contentType || ""; sliceSize = sliceSize || 512; var byteCharacters = window.atob(b64Data); var byteArrays = []; for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) { var slice = byteCharacters.slice(offset, offset + sliceSize); var byteNumbers = new Array(slice.length); for (var i = 0; i < slice.length; i++) { byteNumbers[i] = slice.charCodeAt(i); } var byteArray = new Uint8Array(byteNumbers); byteArrays.push(byteArray); } var blob = new Blob(byteArrays, {type: contentType}); return blob; },
copy(file) { const reader = new FileReader(); reader.onload = (e) => { const img = new Image(); img.src = e.target.result; img.onload = () => { let base64 = this.imageBase64(img); let blob = this.base64ToBlob(base64.replace("data:image/png;base64,", ""), "image/png"); navigator.clipboard.write([ new ClipboardItem({ "image/png": blob, }), ]); }; }; reader.readAsDataURL(file); },
|
writeText()
写入文本至操作系统剪贴板。returning a Promise which is resolved once the text is fully copied into the clipboard。
1
| await navigator.clipboard.writeText('xxx')
|
Document.execCommand() 方法
Document.execCommand()是操作剪贴板的传统方法,
各种浏览器都支持。
它支持复制、剪切和粘贴这三个操作。
document.execCommand(‘copy’)(复制)
1 2 3
| const inputElement = document.querySelector('#input'); inputElement.select(); document.execCommand('copy');
|
document.execCommand(‘cut’)(剪切)
1 2 3
| const inputElement = document.querySelector('#input'); inputElement.select(); document.execCommand('cut');
|
document.execCommand(‘paste’)(粘贴)
1 2 3
| const pasteText = document.querySelector('#output'); pasteText.focus(); document.execCommand('paste');
|
实际操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
function copyToClipboard(text) { var textArea = document.createElement("textarea"); textArea.value = text; document.body.appendChild(textArea); textArea.select(); try { var successful = document.execCommand("copy"); var msg = successful ? "successful" : "unsuccessful"; console.log("Copying text command was " + msg); } catch (err) { console.log("Oops, unable to copy"); } document.body.removeChild(textArea); }
var text = $("#txt").val(); if (text.length == 0) return; copyToClipboard(text);
|
缺点
Document.execCommand()方法虽然方便,但是有一些缺点。
首先,它只能将选中的内容复制到剪贴板,无法向剪贴板任意写入内容。
其次,它是同步操作,如果复制/粘贴大量数据,页面会出现卡顿。有些浏览器还会跳出提示框,要求用户许可,这时在用户做出选择前,页面会失去响应。
扩展
比如在一些平台复制一些内容的时候,人家会在后面加一段自己站点的介绍啥的,这个还可以用paste事件来搞:
1 2 3 4 5
| document.addEventListener('paste', async (e) => { e.preventDefault(); const text = await navigator.clipboard.readText(); console.log('Pasted text: ', text); });
|
原理: 用户使用剪贴板数据,进行粘贴操作时,会触发paste
事件。
另外比如要实现,例如钉钉、飞书里面那种拷贝图片,然后能去别的平台粘贴的功能,也可以借助:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const clipboardItems = [];
document.addEventListener('copy', async (e) => { e.preventDefault(); try { let clipboardItems = []; for (const item of e.clipboardData.items) { if (!item.type.startsWith('image/')) { continue; } clipboardItems.push( new ClipboardItem({ [item.type]: item, }) ); await navigator.clipboard.write(clipboardItems); console.log('Image copied.'); } } catch (err) { console.error(err.name, err.message); } });
|
相当于我们截获一下复制了类似图片,然后我们从原有的自定义的格式啥的,转换成了image/png
这种格式,然后再写入到剪贴板里面去。
其他文章
(本文略扩展了一些内容) 原文地址:https://blog.fedfans.com/2023/03/09/javascript-copy-text/