1. 间接写入clipboard方式(document.execCommand())
// 创建图片
const img = document.createElement('img');
img.src="https://github.githubassets.com/images/icons/emoji/unicode/270d.png"
const body = document.body;
body.appendChild(img);
// 创建DOM范围
const myrange = document.createRange();
myrange.setStartBefore(img);
myrange.setEndAfter(img);
myrange.selectNode(img);
// 添加到选区
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(myrange);
// 复制图片
document.execCommand('copy');
// 删除图片清除选区
selection.removeAllRanges();
body.removeChild(img);
这种方式主要用到的是浏览器的文本框相关事件,如果做过富文本编辑器的同学会比较熟悉。 使用DOM2提供的 createRange()方法创建一个DOM范围,并使用 setStartBefore()以img标签为基准设置范围的起点和 setEndAfter() 以img标签为基准设置范围的终点,使用selectNode()包含img标签。 Selection对象利用DOM范围来管理选区,addRange把给定的DOM范围添加到选区。 与选区交互的方法是用document.execCommand(),这里主要是用复制功能。这个api除了复制,还可以剪贴、粘贴、添加标签、删除、修改样式。注意,为了保证Firefox的兼容性,第二个参数应始终为false。
上面的几步操作也就是clipboard.js的主要流程。它通过document.createElement(‘textarea’);使用文本框的select方法选中,setSelectionRange创建选区。除此之外,contenteditable的div标签则通过跟上文复制图片相同的方式,创建DOM范围,并添加到选区。最后执行操作的也是document.execCommand()。 下面是clipboard.js的选择操作。
function select(element) {
var selectedText;
if (element.nodeName === 'SELECT') {
element.focus();
selectedText = element.value;
}
else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {
var isReadOnly = element.hasAttribute('readonly');
if (!isReadOnly) {
element.setAttribute('readonly', '');
}
element.select();
element.setSelectionRange(0, element.value.length);
if (!isReadOnly) {
element.removeAttribute('readonly');
}
selectedText = element.value;
}
else {
if (element.hasAttribute('contenteditable')) {
element.focus();
}
var selection = window.getSelection();
var range = document.createRange();
range.selectNodeContents(element);
selection.removeAllRanges();
selection.addRange(range);
selectedText = selection.toString();
}
return selectedText;
}
当我自信满满认为这个方案已经很好,现实给了我一击。 PC微信端的富文本编辑器无法识别到通过execCommand复制的内容,猜测是微信做了过滤。 于是我查了一下w3c的规范,https://www.w3.org/TR/clipboard-apis/#copy-action
Copy the selected contents, if any, to the clipboard. Implementations should create alternate text/html and text/plain clipboard formats when content in a web page is selected.
大概意思是通过选择后执行execCommand复制的内容格式只有text/html和text/plain。
复制的图片类型是text/html,猜测PC微信的富文本编辑器是对此做了过滤,所以无法粘贴。 如果业务需求跟微信相关,那么大概可以放弃这种方式了。我没有找到把选区转换成image/png类型去复制的方法…
2. 通过canvas直接写入clipboard
const imgURL = 'https://github.githubassets.com/images/icons/emoji/unicode/270d.png';
const img = document.createElement('img');
img.src = imgURL;
img.crossOrigin="Anonymous";
img.onload = function () {
copyImg(img);
}
function copyImg(img) {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const context = canvas.getContext('2d');
context.drawImage(img, 0, 0, img.width, img.height);
if (canvas.toBlob) {
canvas.toBlob((blob) => {
const clipboardItem = new ClipboardItem({[blob.type]: blob});
navigator.clipboard.write([clipboardItem]);
});
}
}
- 此种方式是通过canvas作为中间转换,调用canvas.toBlob。有了Blob可以很方便构造ClipboardItem对象,作为clipboard.write的参数,写入到clipboard。
3.通过fetch直接写入clipboard
const imgURL = 'https://github.githubassets.com/images/icons/emoji/unicode/270d.png';
const text = 'Hello, world!'
const imgRes = await fetch(imgURL);
const imgBlob = await imgRes.blob();
const clipboardItemImg = new ClipboardItem({[imgBlob.type]: imgBlob});
navigator.clipboard.write([clipboardItemImg])
-
借用fetch方法,返回Response对象,可以把图片转换成Blob。
-
注意
-
clipboard.write和ClipboardItem 在 chrome 66版本开始支持。
-
图片源地址需要支持跨域。
-
clipboard.write入参是个数组,像是支持多个clipboardItem,可以复制图文样子。现实是塞入多个clipboardItem实例就会报错…
Uncaught (in promise) DOMException: Support for multiple ClipboardItems is not implemented.
-
-
总结
- 间接写入clipboard方式功能最强大(可以复制图文,并且可以保留样式),兼容性最好,但是微信的富文本编辑器会过滤text/html类型,在很多业务场景用不了。
- 两种直接写入clipboard的方式简单快捷,不用操作DOM,不用创建Range、Selection对象,但兼容性不佳,只能复制图片,无法图文一起复制。