触摸滚动

iOS 滑动不流畅

上下滑动页面会产生卡顿,手指离开页面,页面立即停止运动。整体表现就是滑动不流畅,没有滑动惯性。原来在 iOS 5.0 以及之后的版本,滑动有定义有两个值 auto 和 touch,默认值为 auto。

-webkit-overflow-scrolling: touch; /* 当手指从触摸屏上移开,会保持一段时间的滚动 */
-webkit-overflow-scrolling: auto; /* 当手指从触摸屏上移开,滚动会立即停止 */

简单的解决办法就是将-webkit-overflow-scrolling 值设置为 touch,也设置滚动条隐藏:.container ::-webkit-scrollbar {display: none;};不过这样可能会导致使用position:fixed; 固定定位的元素,随着页面一起滚动。搭配可以设置外部 overflow 为 hidden,设置内容元素 overflow 为 auto。内部元素超出 body 即产生滚动,超出的部分 body 隐藏。

body {
  overflow-y: hidden;
}
.wrapper {
  overflow-y: auto;
}

iOS 上拉边界下拉出现白色空白

手指按住屏幕下拉,屏幕顶部会多出一块白色区域。手指按住屏幕上拉,底部多出一块白色区域。在 iOS 中,手指按住屏幕上下拖动,会触发 touchmove 事件。这个事件触发的对象是整个 webview 容器,容器自然会被拖动,剩下的部分会成空白。我们可以通过监听 touchmove,让需要滑动的地方滑动,不需要滑动的地方禁止滑动。

document.body.addEventListener(
  "touchmove",
  function (e) {
    if (e._isScroller) return;
    // 阻止默认事件
    e.preventDefault();
  },
  {
    passive: false,
  }
);

在很多时候,我们可以不去解决这个问题,换一直思路。根据场景,我们可以将下拉作为一个功能性的操作。

页面放大或缩小不确定性行为

双击或者双指张开手指页面元素,页面会放大或缩小。HTML 本身会产生放大或缩小的行为,比如在 PC 浏览器上,可以自由控制页面的放大缩小。但是在移动端,我们是不需要这个行为的。所以,我们需要禁止该不确定性行为,来提升用户体验。HTML meta 元标签标准中有个 中 viewport 属性,用来控制页面的缩放,一般用于移动端。

<meta name="viewport" content="width=device-width, initial-scale=1.0" />

因此我们可以设置 maximum-scale、minimum-scale 与 user-scalable=no 用来避免这个问题:

<meta
  name="viewport"
  content="width=device-width, initial-scale=1.0, minimum-scale=1.0 maximum-scale=1.0, user-scalable=no"
/>

click 点击事件延时与穿透

  • 监听元素 click 事件,点击元素触发时间延迟约 300ms。iOS 中的 safari,为了实现双击缩放操作,在单击 300ms 之后,如果未进行第二次点击,则执行 click 单击操作。也就是说来判断用户行为是否为双击产生的。但是,在 App 中,无论是否需要双击缩放这种行为,click 单击都会产生 300ms 延迟。

  • 点击蒙层,蒙层消失后,下层元素点击触发。双层元素叠加时,在上层元素上绑定 touch 事件,下层元素绑定 click 事件。由于 click 发生在 touch 之后,点击上层元素,元素消失,下层元素会触发 click 事件,由此产生了点击穿透的效果。

首先可以使用 touchstart 替换 click,将 click 替换成 touchstart 不仅解决了 click 事件都延时问题,还解决了穿透问题。因为穿透问题是在 touch 和 click 混用时产生。

el.addEventListener(
  "touchstart",
  () => {
    console.log("ok");
  },
  false
);

不过需要注意的是,我们并不可以将 click 事件全部替换成 touchstart。因为事件触发顺序: touchstart, touchmove, touchend, click,很容易想象,在我需要 touchmove 滑动时候,优先触发了 touchstart 的点击事件,会产生冲突。所以呢,在具有滚动的情况下,还是建议使用 click 处理。在接下来的 fastclick 开源库中也做了如下处理。针对 touchstart 和 touchend,截取了部分源码。

// If the target element is a child of a scrollable layer (using -webkit-overflow-scrolling: touch) and:
// 1) the user does a fling scroll on the scrollable layer
// 2) the user stops the fling scroll with another tap
// then the event.target of the last 'touchend' event will be the element that was under the user's finger
// when the fling scroll was started, causing FastClick to send a click event to that layer - unless a check
// is made to ensure that a parent layer was not scrolled before sending a synthetic click (issue #42).
this.updateScrollParent(targetElement);

// Don't send a synthetic click event if the target element is contained within a parent layer that was scrolled
// and this tap is being used to stop the scrolling (usually initiated by a fling - issue #42).
scrollParent = targetElement.fastClickScrollParent;
if (
  scrollParent &&
  scrollParent.fastClickLastScrollTop !== scrollParent.scrollTop
) {
  return true;
}

主要目的就是,在使用 touchstart 合成 click 事件时,保证其不在滚动的父元素之下。

下一页