触摸滚动

iOS滑动不流畅

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

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

简单的解决办法就是将-webkit-overflow-scrolling值设置为touch,也设置滚动条隐藏:.container ::-webkit-scrollbar {display: none;};不过这样可能会导致使用position:fixed; 固定定位的元素,随着页面一起滚动。搭配可以设置外部overflowhidden,设置内容元素overflowauto。内部元素超出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-scaleminimum-scaleuser-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事件,点击元素触发时间延迟约300msiOS中的safari,为了实现双击缩放操作,在单击300ms之后,如果未进行第二次点击,则执行click单击操作。也就是说来判断用户行为是否为双击产生的。但是,在App中,无论是否需要双击缩放这种行为,click单击都会产生300ms延迟。

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

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

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

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

// 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事件时,保证其不在滚动的父元素之下。

下一页