移动端项目中公司框架默认引入了 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
粉丝