缘起
长按复制功能是一个比较常见的需求(可能吧),但是我在 npm 仓库中找了半天也没有找到,事出反常必有妖,在我研究了半天以后,写了这篇文章告诉大家: 这个功能,移动端,做不了!
从长按开始
首先我们从长按开始讲,在常见的手势库中,都有长按的实现,一般都是监听按下事件,然后运行一个 setTimeout, 或者记录一个时间戳 然后在取消长按的时候判断,时间戳是否到达阈值,或者取消timeout事件。还有一系列细节,比如要在鼠标移出的时候取消事件。
这里首先要注意第一个坑,那就是如果你要支持PC端和移动端,最好使用 pointerdown/pointerup 事件组合来实现。
PC端只支持
[mousedown/mouseup, pointerdown/pointerup]移动端三个事件都支持,但是mousedown的触发时机会被延后,而且会被浏览器默认长按事件取消(长按时间在大约1.5秒内,会自动触发mousedown/mouseup事件,无间隔连续触发)
这里我试用了下掘金的代码片段,可以参考:长按实现
长按就是这么简单,市场上也有很多开源实现。接下来我们看复制怎么实现。
复制文本实现
复制的实现方法有三种
- 使用
navigator.clipboard实现 - 使用
document.execCommand实现,已废弃⚠️ 使用 flash 插件实现 (什么远古方案
先看第一种实现方案,也是现代浏览器推荐的这种。上代码
function copy(text) {
return new Promise((resolve, reject) => {
if (navigator.clipboard) {
return navigator.clipboard.writeText(text).then(resolve).catch(reject)
}
return reject(new Error('浏览器不支持,请手动选择复制'))
})
}
对兼容性有需求的,可以使用document.execCommand 来实现 fallback。(事实上,兼容clipboard的浏览器占比更多)
然后就是加上触发条件,如果你这时候直接使用长按来触发复制,可能会发现移动端出现了复制失败问题,为了控制变量,我们先使用click 事件来触发复制,看看我们的复制代码是否有误。
如果你直接在文章里使用这个代码片段,你会发现你一直复制失败。这是因为掘金给代码片段的iframe 没有添加 allow 头。<iframe allow="clipboard-write;clipboard-read" src='xxx'> 不知道是出于安全考虑还是忘了。
还有一点需要注意,大部分浏览器都默认允许写剪贴板特性,但是读剪贴板是要用户授权的。所以如果不是必要,就不要读取剪贴板。实在要读取,可以先用用户权限查询是否有权限,再做权衡。查询权限(注意兼容性)
移动端 长按 复制 失败?
复制代码调通了,接下来接入我们的长按事件,这时候我们会发现,PC端正常,移动端失效了!
打印我们的错误看看
The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission.
大概意思就是我们没有权限,但是我们刚刚点击事件触发明明是可以的,于是继续搜索。
先看MDN文档,clipboard 里面没有说明,在看 writeText 方法,里面有一句引起了我的注意。
Transient user activation is required. The user has to interact with the page or a UI element in order for this feature to work.
翻译过来就是 用户瞬时活跃态(姑且这么翻译)的情况下才可以使用 writeText,那么这个 Transient user activation 又是什么呢,继续看文档。
大概就是为了保护某些 API 不被滥用,所以对其使用进行了限制,而我们要用的 writeText 也在其中。
但是我们再看user activation标准 里面说到了pointerup 时且isTrusted && pointerType !== 'mouse' 时是会触发的。但是我们却用不了,其他事件也一样,这里猜测是因为移动端就用了touch来进行判定,因为 pointer 事件是很频繁的,所以只有pointerdown会触发,而且加了较短的时限(大约750ms以内)和同步的限制。
所以目前来看就是进入了死循环,要长按就会失去用户瞬时活跃态,而只有用户瞬时活跃态才能调用复制。当然,如果有人成功实现了,务必评论一下。
不过移动端也不太需要这个功能,大部分浏览器本身就自带了长按选中,用户只要点一下复制就可以了。
所有的测试代码如下