移动端项目中公司框架默认引入了 fastclick.js。因为业务需要,同时引入了 ant-design 中的
select
组件,导致在 iOS 端,select 组件需要双击才能弹出选项。
通过对这个问题进行深入研究,发现是 fastclick 导致的问题。
DOM 事件触发顺序
首选需要了解一下移动端
click
,鼠标事件
mouse
以及触摸事件
touch
的触发顺序:
onTouchStart => (onTouchmove) => onTouchEnd => mousedown => (mousemove) => mouseup => click
fastclick 机制
通过查看 fastclick 源码得知:
fastclick 会在 onTouchEnd
中调用了 event.preventDefault()
阻止默认事件(会阻止后续的 mouse
、click
事件的触发)。
并创建并触发自定义的 click
事件(对于原生的 select
元素则触发 mousedown
事件)
通过上述分析可知,如果元素不是原生的 select
组件,则不会触发 mouse
事件。
对于 onTouchStart
及 onTouchEnd
中调用 event.preventDefault()
阻止的默认事件,可参考:Touch event -- mdn
ant-design select 是如何触发选项弹出的
通过查看 ant-design 使用到的 rc-select
源码,得知是模拟了原生的 select
,使用了 mousedown
事件触发弹出选项,但内部并没有使用 select
元素,而是通过 div
元素进行模拟的:
const onInternalMouseDown: React.MouseEventHandler<HTMLDivElement> = (event, ...restArgs) => {
if (onMouseDown) {
onMouseDown(event, ...restArgs);
return (
className={mergedClassName}
{...domProps}
ref={containerRef}
onMouseDown={onInternalMouseDown}
onKeyDown={onInternalKeyDown}
onKeyUp={onInternalKeyUp}
onFocus={onContainerFocus}
onBlur={onContainerBlur}
{mockFocused && !mergedOpen && (
style={{
width: 0,
height: 0,
display: 'flex',
overflow: 'hidden',
opacity: 0,
aria-live="polite"
{/* Merge into one string to make screen reader work as expect */}
{`${mergedRawValue.join(', ')}`}
</span>
{selectorNode}
{arrowNode}
{clearNode}
</div>
组件的实现位于 react-component/select
中,文件地址:github.com/react-compo…
fastclick 不能识别组件为原生的 select
,导致 dispatch
了 click
而不是 mousedown
事件,进而单击无反应。
为何双击可以触发
通过进一步查看源码,了解到 fastclick 对双击事件进行了特殊处理,当两次点击低于延迟 250ms
(fastclick 默认是否为双击判断时间),当双击后会触发 fastclick 对双击事件进行处理。首先,在 onTouchStart
中:
FastClick.prototype.onTouchStart = function(event) {
if ((event.timeStamp - this.lastClickTime) < this.tapDelay) {
event.preventDefault();
虽然 onTouchStart
中调用了 event.preventDefault()
,但是并不能阻止后续事件的触发。移动端为了让滚动能够更快的响应,所以浏览器对于 onTouchStart
事件默认设置 passive: true
,即调用 event.preventDefault()
会被忽略(chrome 56+)。
具体内容参考:Making touch scrolling fast by default
在后续的 onTouchEnd
中,也对双击进行了判断:
FastClick.prototype.onTouchEnd = function(event) {
if ((event.timeStamp - this.lastClickTime) < this.tapDelay) {
this.cancelNextClick = true;
return true;
onTouchEnd
通过 return true
阻止了后续自定义事件的触发,导致后续原生的 mousedown
事件能够触发,进而 ant-design 的触发 select
的 onMouseDown
事件。
因为项目不需要兼容老旧的浏览器,并且 <header>
中已经设置了:
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
项目中不需要 fastclick 来进行兼容,所以最后直接干掉了 fastclick
更多移动端 300ms 解决方案:5 way prevent 300ms click delay mobile devices
niayyy
前端 @ @
24.3k
粉丝