相信很多人做需求过程中,都遇到过把文字复制到剪贴板的功能。很不幸我也遇到了,本以为是一个简简单单的需求,开发测试过程中却遇到了不少坑,这里一一展开。
execCommand
方法
第一次,我使用了选中文字 execCommand
这个方法来实现,代码如下:
const copyText = (val) => {
const textArea = document.createElement('textArea')
textArea.value = val
document.body.appendChild(textArea)
textArea.select()
document.execCommand('copy')
document.body.removeChild(textArea)
}
创建了一个textArea
DOM 元素,插入到 body
中,再选中 textArea
的内容,执行 document.execCommand('copy')
,最后在移除 textArea
。
写完一跑,没问题,完美,就提测了。谁承想,提测之后,测试提出来一个 bug。
在手机 Safari 浏览器中,点击复制按钮,整个页面会跳动一下。
这是什么神奇 bug?
经排查(two thousand years later),创建的textArea
不在页面可视区域之内,然后执行textArea.select()
,就会触发浏览器的控件跳转行为,页面会滚动到 textArea
所在位置。然后执行完又快速移除了,就会形成闪动的这么一个现状。
知道问题了,那就很好解决了,给元素增加绝对定位。
于是我修改代码如下:
const textArea = document.createElement('textArea')
textArea.value = val
textArea.style.width = 0
textArea.style.position = 'fixed'
textArea.style.left = '-999px'
textArea.style.top = '10px'
textArea.setAttribute('readonly', 'readonly')
document.body.appendChild(textArea)
textArea.select()
document.execCommand('copy')
document.body.removeChild(textArea)
增加了一个 fixed 定位,left 是-999px,top 是 10 px,保存代码运行测试,ok 完美。
这时候我有查了下execCommand
的 MDN,developer.mozilla.org/zh-CN/docs/…
该特性已经被弃用,之所以还能用,是因为有些浏览器还没删除实现。可能不知道哪天就删了,也可能永远不删。
这不太行啊
navigator.clipboard
于是,我用上了 navigator.clipboard
,可能兼容性还不太好,所以代码这么写:
if (navigator.clipboard) {
await navigator.clipboard.writeText(val)
}
测试一下,完美,于是完整代码变成了这样:
const copyText = async (val) => {
if (navigator.clipboard) {
await navigator.clipboard.writeText(val)
} else {
const textArea = document.createElement('textArea')
textArea.value = val
textArea.style.width = 0
textArea.style.position = 'fixed'
textArea.style.left = '-999px'
textArea.style.top = '10px'
textArea.setAttribute('readonly', 'readonly')
document.body.appendChild(textArea)
textArea.select()
document.execCommand('copy')
document.body.removeChild(textArea)
}
}
优先使用 navigator.clipboard
,如果不存在再降级使用 execCommand
。
又兴高采烈的提测了。
bug 它又来了。
“你来啦,土星,不是,是 bug”
在安卓 APP 的 WebView 中,点击复制按钮没有反应。
我特发?
继续排查(又 two thousand years later)。
点击复制的时候,报错Write permission denied
。
查看 MDN:developer.mozilla.org/zh-CN/docs/…
只有在用户事先授予网站或应用对剪切板的访问许可之后,才能使用异步剪切板读写方法。许可操作必须通过取得权限 Permissions API 的 "clipboard-read" 和/或 "clipboard-write" 项获得。
Navigator 这种新 API 都是需要事先授予权限的,而权限是通过 Permissions API
获取的。
看这里,原来 Permissions API
在安卓的 WebView 中是没实现的。
CGQAQ 同学帮我找了一下 android_webview 的源码
果不其然,这里并没实现,直接返回了PermissionStatus::DENIED
状态。
那么我们再在代码里加一个Permissions API
的判断。
if (navigator.clipboard && navigator.permissions) {
await navigator.clipboard.writeText(val)
}
完整代码如下:
const copyText = async (val) => {
if (navigator.clipboard && navigator.permissions) {
await navigator.clipboard.writeText(val)
} else {
const textArea = document.createElement('textArea')
textArea.value = val
textArea.style.width = 0
textArea.style.position = 'fixed'
textArea.style.left = '-999px'
textArea.style.top = '10px'
textArea.setAttribute('readonly', 'readonly')
document.body.appendChild(textArea)
textArea.select()
document.execCommand('copy')
document.body.removeChild(textArea)
}
}
调用 APP 方法
还有一种解决方案,就是由安卓端实现一个供 JS 调用的复制到剪贴板方法,JS 判断是否在 APP 内,进行调用。
此方案需要多端协调,考虑情况暂未采用,其他人遇到此问题的可以酌情使用。
最后
又提测了,希望不会出现新的 bug。
参考文献:
本文首发于:sayhub.me/blog/copy-t…