【问题标题】:How to prevent default handling of touch events?如何防止默认处理触摸事件?
【发布时间】:2020-05-16 12:37:46
【问题描述】:

我正在尝试做类似于embedded Google maps 所做的事情。我的组件应该忽略单点触摸(允许用户滚动页面)并在其外部捏合(允许用户缩放页面),但应该对双点触摸做出反应(允许用户在组件内部导航)并在这种情况下禁止任何默认操作。

如何防止默认处理触摸事件,但仅限于用户用两根手指与我的组件交互的情况?

我尝试过的:

  1. 我尝试捕获onTouchStartonTouchMoveonTouchEnd。事实证明,在 FF Android 上,当在组件上进行捏合时触发的第一个事件是 onTouchStart 一次触摸,然后是 onTouchStart 两次触摸,然后是 onTouchMove。但是在onTouchMove 处理程序中调用event.preventDefault()event.stopPropagation() 不会(总是)停止页面缩放/滚动。在第一次调用onTouchStart 时防止事件升级确实有帮助 - 不幸的是,当时我还不知道它是否会是多点触控,所以我不能使用它。 p>

  2. 第二种方法是在document.body 上设置touch-action: none。这适用于 Chrome Android,但如果我在所有元素(除了我的组件)上设置它,我只能使其适用于 Firefox Android。因此,虽然这是可行的,但似乎它可能会产生不必要的副作用和性能问题。 编辑: 进一步的测试表明,只有在触摸开始之前设置了 CSS 时,这才适用于 Chrome。换句话说,如果我在检测到 2 个手指时注入 CSS 样式,那么 touch-action 将被忽略。所以这在 Chrome 上没有用。

  3. 我也尝试在组件挂载上添加一个新的事件监听器:

    document.body.addEventListener("touchmove", ev => {
      ev.preventDefault();
      ev.stopImmediatePropagation();
    }, true);
    

    touchstart 也是如此)。这样做在 Firefox Android 中有效,但在 Chrome Android 上无效。

我的想法不多了。是否有一种可靠的跨浏览器方式来实现谷歌显然所做的事情,或者他们是否在每个浏览器上使用了多种黑客技术和大量测试来使其正常工作?如果有人指出我的方法中的错误或提出新方法,我将不胜感激。

【问题讨论】:

    标签: javascript reactjs cross-browser


    【解决方案1】:

    TL;DR:我在注册事件处理程序时缺少{ passive: false }

    我在使用 Chrome 时遇到的preventDefault() 问题是由于他们的scrolling "intervention"(阅读:破坏网络 IE 风格)。简而言之,因为不调用preventDefault() 的处理程序可以更快地处理,所以在addEventListener 中添加了一个名为passive 的新选项。如果设置为true,则事件处理程序承诺不调用preventDefault(如果是,则调用将被忽略)。然而,Chrome 决定更进一步,将 {passive: true} 设为默认值(从 56 版开始)。

    解决方案是调用事件侦听器,并将passive 显式设置为false

    window.addEventListener('touchmove', ev => {
      if (weShouldStopDefaultScrollAndZoom) {
        ev.preventDefault();
        ev.stopImmediatePropagation();
      };
    }, { passive: false });
    

    请注意,这会对性能产生负面影响。

    附带说明,我似乎误解了touch-action CSS,但我仍然无法使用它,因为它需要在触摸序列开始之前设置。但如果不是这种情况,它可能性能更高,似乎是supported on all applicable platforms(Mac 上的 Safari 不支持它,但在 iOS 上它支持)。 This post 说得最好:

    对于您的情况,您可能想要标记您的文本区域(或其他) 'touch-action: none' 禁用滚动/缩放而不禁用 所有其他行为。

    CSS 属性应该设置在组件上,而不是像我那样设置在 document 上:

    <div style="touch-action: none;">
      ... my component ...
    </div>
    

    在我的情况下,我仍然需要使用 passive 事件处理程序,但很高兴知道这些选项...希望它对某人有所帮助。

    【讨论】:

    • 我真的很想从中得到一个更简洁的答案。现在,我实现了一个额外的 touchmove 事件并将 css 添加到组件中。两者都是必要的还是我们可以不使用组件上的 css 吗?
    • 只需要其中一个。 CSS touch-action 选项性能更高(也更好),但它只有在您提前设置时才有效(在touchstart 事件之前)。在我的情况下,这不是一个选项,因为我需要根据手指的数量来采取不同的行为,所以我必须等待 touchstart。
    【解决方案2】:

    尝试使用 if 语句查看是否有多个触摸:

    document.body.addEventListener("touchmove", ev => {
      if (ev.touches.length > 1) {
        ev.preventDefault();
        ev.stopImmediatePropagation();
      }
    }, true);
    

    【讨论】:

    • 谢谢!我已经尝试过了(请参阅我的问题中的第 3 点)。但是,这并不妨碍在 Chrome for Android 上滚动/缩放。请注意,我无法阻止默认操作(在检测到 2 个手指后),不知道是否需要阻止它。
    【解决方案3】:

    这是我的想法:

    我用divopacity: 0 覆盖mapz-index > 映射z-index

    我会在covered div 中检测到。如果我检测到2 fingers 在此covered div 中触摸,我将display: nonediv 允许用户在此地图中使用2 个手指。

    否则,在document touchEnd 中,我将使用display: block 恢复这个covered div 以确保我们可以滚动。

    【讨论】:

      【解决方案4】:

      在我的情况下,我已经解决了

      @HostListener('touchmove', ['$event'])
          public onTouch(event: any): void {
          event.stopPropagation();
          console.log('onTouch', event);
      }
      

      【讨论】:

        猜你喜欢
        • 2013-03-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-07-09
        • 1970-01-01
        • 2011-10-08
        • 1970-01-01
        相关资源
        最近更新 更多