msdlisper Front-end

移动端的click

2017-12-27

我想问移动端的点击事件有多坑..

引入

很happy, 不久前, 入坑移动端, 发现iso上点击有300ms延迟, 于是就网上找了下相关资料,发现了写问题:

300ms延迟, 你从哪里来?

让我们回到2007, iphone就要发布了, 苹果的工程师们为了让小屏幕的iphone能访问pc的网页, 聪明的决定: click事件加上300ms延迟来区分用户的双击缩放动作…

然后…各大手机浏览器厂商争相效仿…就成了现在的样子(是说老乔爱搞专利, 抄的人太多了)

这样带来的问题也非常明显, 那就是所有基于click交互的元素都受到影响, 大家的直观感受就是: 慢!!

因为100ms的延迟就会被人察觉

怎么解决

方案一 禁止缩放

  • 代码
<meta name="viewport" content="user-scalable=no">
<!-- 或者 -->
<meta name="viewport" content="initial-scale=1, minimum-scale=1, maximum-scale=1">
  • 原理: 上面的代码是禁止缩放, 延迟就是为了缩放, 那都没延迟了, 缩放也就没有必要了
  • 支持情况: 在 Android 平台上,由 Chrome 最先提出,FireFox、Opera 等浏览器也相继支持;IOS 9.3 开始一度支持,IOS10 开始不再支持
  • safari不支持. 而且禁止缩放会损害网站的可访问性(which may be an accessibility concern)

方案二 视窗宽度设置为设备宽度

  • 代码
<meta name="viewport" content="width=device-width">

  • 原理: iPhone 诞生时就有的另一个约定是, 在渲染桌面端站点的时候, 使用 980 像素的视口宽度,而非设备本身的宽度(iPhone 是 320 像素宽), 上面代码告诉浏览器将视口大小设为设备本身的尺寸, 然后在 Chrome 32 这一版中, 他们将在包含 width=device-width 或者置为比 viewport 值更小的页面上禁用双击缩放, 没有必要双击, 就没有延迟了
  • 支持情况: 自 Chrome 32 开始, FF,IE/Edge 也随后支持了; 2016 年 3 月,IOS 9.3 开始支持。

该方案的”禁止双击缩放”是遵守的规则

  • 当页面设置了视窗宽度且是初始尺寸(页面未缩放), 双击缩放才是被禁止的
  • 为了让用户结束后仍然能fast-click, 缩小时,只能缩小到初始尺寸, 不是最小尺寸
  • 为什么不直接使用方案二?
    • 可以看看ant-disign的使用
      • 引入fastclick解决问题576
      • iso 9.3以前的会有问题?毕竟iphone6s 15年9月发布, iso 9.3 16年3月发布, 用方案二还是有延迟
      • 代码
        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
      <script src="https://as.alipayobjects.com/g/component/fastclick/1.0.6/fastclick.js"></script>
      

方案三 指针事件

  • 代码
button, .yourclass {
    -ms-touch-action: manipulation; /* IE10 */
    touch-action: manipulation; /* IE11 */
}
  • 根据w3c规范 MDN: touch-action的manipulation激活了平移和双指缩放手势, 而禁用了双击缩放等非标准的手势.
  • 支持情况: Opera Mini不支持, FF需要手动启动,iso上,baiduhi_ios/6.7.1不支持

方案四 FastClick

  • 代码
window.addEventListener( "load", function() {
    FastClick.attach( document.body ); // 直接绑定到 <body> 上可以确保整个应用都能受益
}, false );
  • 原理: FastClick监听touchend事件, 通过DOM自定义事件立即触发一个模拟的click事件, 并把300ms之后的click事件阻止掉
  • FastClick会检测你是否用了其它方案, 避免冲突
  • 缺点和已知问题:
    • fastclick是监听touchend, 但如果我再监听touchend, 比如列表滑动的实现, fastclick会误认为是一次点击, 我在android(目前所有机型)上有这个问题178
    • label和input 也不能正确的绑定460
    • github 上似乎很久没人搭理了..

最后使用的

  • 本来想使用方案三, 但无奈ios的百度浏览器不支持..
  • 于是暂时先用4

Safari在这个问题上

可以看出, 方案1,2,3都是ios不友好的. 2016 年 3 月发布的 IOS 9.3 移除了 300ms 延迟、从而实现了fast-tap, 但IOS10 无视了禁用缩放(user-scalable), 所有单靠<meta>来解决延迟, 不是很靠谱. 谁知道safari后面会有什么新的想法.

点击穿透

其实我还没有遇到过(没用过Zepto, 不知有此坑), 但这个问题和300ms有关

来历

PC上的点击是鼠标事件, 一次点击行为的触发过程是: mousedown -> mouseup -> click

手机上一次点击是 touchstart -> touchend ————–> mousedown -> click -> mouseup 也会响应鼠标事件, 只是有300ms延迟. 而且没有像PC端的click一样的事件(touchend并代替不了), 于是许多js库(Zepto, JQuery)就来模拟一个click, 叫tap事件, 并, 引出了点击穿透.

示例

  • jsfidden
  • codepen

See the Pen 点击穿透 by msdlisper (@msdlisper) on CodePen.

解释

  • 结合Zepto源码, tap事件是通过监听document上的touch事件来完成tap的模拟, 是通过事件冒泡实现的, 但touchstart/touchend会触发click事件的, 因为click事件有延迟, 所以300ms后, 触发到屏幕上最顶上的元素的click事件
  • 好,来理一下思路: 现有A,B两层, A在B上. 我点A -> 触发A touchstart -> touchend -> tap -> A(), 让A立马消失 —-300ms后—> click触发 -> 因为A不在了, 锅就这样甩给了B -> B.click() -> 完成了从点击A到穿透B的过程

解决

  • 最直接的方法: fastclick, 让click延迟消失
  • 用CSS3的 poiner-events: none, 元素不再是鼠标事件的目标, 对click不感冒
  • 加一个中间层C, 让A消失后, C来背锅

参考


Similar Posts

Comments