copy-to-clipboard 源码解析,隐藏的内容比想象的要多
本文针对的源码版本为:193826f
copy-to-clipboard 是一个 js 的剪切板库,可用来复制内容到剪切板,看源码后发现其中隐藏的内容着实不少,今天一起来解读下其源码。
使用方式
我们先看下使用方式:
import copy from 'copy-to-clipboard';
copy('Text');
// Copy with options
copy('Text', {
debug: true,
message: 'Press #{key} to copy'
});
可以看到 API 非常简单。
该库只抛出一个 copy 函数,函数接口为:copy(text: string, options: object): boolean。
第一个参数为一个文本值为用来复制的内容。
options 包含 4 个参数:
- debug - 是否开启调试模式,开启后会将复制信息打印到 console 中
- message - 通过 prompt 模式兼容时的提示信息
- format - 设置复制内容的 mime type
- onCopy - 复制后的回调,接口为:function onCopy(clipboardData: object): void
源码解读
该库源码比较简单,其中使用到了一个依赖库:toggle-selection,作用是取消和恢复当前选中的文本的选中状态。
主体方案
先看看主体部分:
reselectPrevious = deselectCurrent();
range = document.createRange();
selection = document.getSelection();
mark = document.createElement('span');
mark.textContent = text;
// ...
mark.addEventListener('copy', function (e) {
// ...
});
document.body.appendChild(mark);
range.selectNodeContents(mark);
selection.addRange(range);
var successful = document.execCommand('copy');
if (!successful) {
throw new Error('copy command was unsuccessful');
}
success = true;
这部分首先取消了当前页面中已有的选择状态,然后创建了一个 range,range 可用来包含文本或节点片段。随后通过 getSelection 获得了一个 Selection 对象,该对象包含当前用户选中的或鼠标所在的内容。
然后它创建了一个 span 元素,将要复制的内容设置为该元素的文本,然后通过各种样式设置等,主要是为了避免该元素被发现、被读取、无法复制等问题。随后它在该元素中添加了 copy 事件,将元素添加到 body,然后通过 range.selectNodeContents 和 selection.addRange 选中该元素,并通过 document.execCommand 调用 copy 命令即可将选中的内容进行复制。
下面再看一下 copy 事件的处理:
e.stopPropagation();
if (options.format) {
e.preventDefault();
if (typeof e.clipboardData === 'undefined') {
// IE 11
debug && console.warn('unable to use e.clipboardData');
debug && console.warn('trying IE specific stuff');
window.clipboardData.clearData();
var format = clipboardToIE11Formatting[options.format] || clipboardToIE11Formatting['default'];
window.clipboardData.setData(format, text);
} else {
// all other browsers
e.clipboardData.clearData();
e.clipboardData.setData(options.format, text);
}
}
if (options.onCopy) {
e.preventDefault();
options.onCopy(e.clipboardData);
}
主要是分两部分,一部分是当存在自定义 format 时会组织默认的复制行为,然后通过 clipboardData.setData 重新设置内容格式,其中包含一部分 IE 兼容的代码。第二部分则是调用 onCopy 回调,这里要注意,使用 onCopy 后它会阻止默认事件,此时你需要自己将内容设置到剪切板中。
兼容方案
在上述使用 execCommand 报错后,它会尝试使用 clipboardData 做第二次尝试,这个主要是针对 IE 所做的兼容:
window.clipboardData.setData(options.format || 'text', text);
options.onCopy && options.onCopy(window.clipboardData);
success = true;
如果该方案依旧失败,则会降级到终极方案 - prompt:
message = format('message' in options ? options.message : defaultMessage);
window.prompt(message, text);
其中 format 主要是为了让 prompt 框的提示信息中的 ctrl 在 mac 中替换为 command。
在老浏览器中 prompt 会弹出弹窗,内容区域显示要复制的信息,然后提示区域提示用户 ctrl+c 复制。
收尾
复制完成后,它会将之前的 selection、range 和 mark 进行清理,并通过 reselectPrevious 恢复之前的选择状态。
if (selection) {
if (typeof selection.removeRange == 'function') {
selection.removeRange(range);
} else {
selection.removeAllRanges();
}
}
if (mark) {
document.body.removeChild(mark);
}
reselectPrevious();
兼容性
该库兼容几乎所有的主流浏览器版本,包括 IE,因为 execCommand 虽然被废弃,但是兼容性很高,然后它还使用了 clipboardData 做 IE 兼容,并且使用了 prompt 做降级方案。
不过文档中提示在一些老的 IOS 设备中,由于无法定制 prompt 内容,所以无法兼容。
总结
该库虽然源码很简短,但是用到了相当多平时编程中很少甚至不会用到的 API,如:
- Range
- Selection
- execCommand
- clipboardData
- style.all
- style.clip
不过代码依然有优化的空间,如大量引用 mark.style 处可创建引用、随处可见的 debug 可做成预处理函数、兼容方案中 onCopy 行为不一致等。