Hammer.js: In Android browsers, panning on child of SVG breaks when dragged more than a few pixels

Created on 15 Jun 2018  ·  6Comments  ·  Source: hammerjs/hammer.js

This problem (bug?) is driving me crazy. I can't find any existing issues on it, but maybe I'm missing something. I hope there is some solution, workaround or bug fix.

Problem

In web browsers on Android, panning breaks for elements inside an <svg> tag. The same problem does not arise when given a <div> the same treatment.

Reproducing the problem

It's nicely shown by this CodeSandBox example, which you obviously have to run in an Android web browser to see it break.

Here is a video showing the issue on a Nexus 5X.

Description of what's happening:

  1. Use HammerJS to start listening to pan events from an element inside a <svg> tag (e.g. a <circle>).
  2. The panstart fires correctly.
  3. As long as you drag no further than 10px away from the starting point, panmove will also trigger correctly.
  4. As soon as you move further away, the panmove will stop firing
  5. The panend will no longer fire (it _will_ if you didn't drag too far)

I tested both on an up-to-date Chrome app and a default browser on an old Android. Same effect.

UPDATE 15 June 2018:
See this thread: it seems someone else is experiencing the same problem and dug deeper than I did. He found out that the pancancel is being triggered, because a pointercancel is being thrown by the browser. Still not clear why this is happening and how to deal with this.

Most helpful comment

By digging deeper into the pancancel and pointercancel, I found a workaround that works fine for me (but your mileage may vary):

For Android browsers, It turns out that preventing the default "touchmove" event handling on the window will solve this problem for any SVG element on the page, as it will prevent the browser from noticing the touch event and assuming the user is scrolling (which triggers the pointercancel, which triggers the pancancel). That's great news, but it also disables all scrolling behaviour on the page. What I've done instead is catching the "touchmove" event on the SVG object, so any descendants in it will pan properly. I'm not yet sure if it has any bad side effects, but so far it has meant that my panning in SVG finally works on Android browsers!

Here's the straightforward code I used, though notice the need for the specific option:

const options = { passive: false }; // needed because Chrome has this set to "true" by default for "touchmove" events
    mySVG.addEventListener(
      "touchmove",
      e => e.preventDefault(),
      options
    );

I will keep this issue listed here, because I think it's something that should be handled (i.e. abstracted away) by HammerJS. For now, I hope people with a similar problem will find this and need less time debugging this one.

All 6 comments

By digging deeper into the pancancel and pointercancel, I found a workaround that works fine for me (but your mileage may vary):

For Android browsers, It turns out that preventing the default "touchmove" event handling on the window will solve this problem for any SVG element on the page, as it will prevent the browser from noticing the touch event and assuming the user is scrolling (which triggers the pointercancel, which triggers the pancancel). That's great news, but it also disables all scrolling behaviour on the page. What I've done instead is catching the "touchmove" event on the SVG object, so any descendants in it will pan properly. I'm not yet sure if it has any bad side effects, but so far it has meant that my panning in SVG finally works on Android browsers!

Here's the straightforward code I used, though notice the need for the specific option:

const options = { passive: false }; // needed because Chrome has this set to "true" by default for "touchmove" events
    mySVG.addEventListener(
      "touchmove",
      e => e.preventDefault(),
      options
    );

I will keep this issue listed here, because I think it's something that should be handled (i.e. abstracted away) by HammerJS. For now, I hope people with a similar problem will find this and need less time debugging this one.

By digging deeper into the pancancel and pointercancel, I found a workaround that works fine for me (but your mileage may vary):

For Android browsers, It turns out that preventing the default "touchmove" event handling on the window will solve this problem for any SVG element on the page, as it will prevent the browser from noticing the touch event and assuming the user is scrolling (which triggers the pointercancel, which triggers the pancancel). That's great news, but it also disables all scrolling behaviour on the page. What I've done instead is catching the "touchmove" event on the SVG object, so any descendants in it will pan properly. I'm not yet sure if it has any bad side effects, but so far it has meant that my panning in SVG finally works on Android browsers!

Here's the straightforward code I used, though notice the need for the specific option:

const options = { passive: false }; // needed because Chrome has this set to "true" by default for "touchmove" events
    mySVG.addEventListener(
      "touchmove",
      e => e.preventDefault(),
      options
    );

I will keep this issue listed here, because I think it's something that should be handled (i.e. abstracted away) by HammerJS. For now, I hope people with a similar problem will find this and need less time debugging this one.

It works and thanks a lot!

Very help full me, thanks!


This problem can reproducing also by PC GoogleChrome(75.0.3770.100) Toggle device toolbar(Ctrl+Shift+M).

いろいろテストしていたところ、e.preventDefault()をかけた場合、二回目以降のpanで [Intervention] Ignored attempt to cancel a touchmove event with cancelable=false, for example because scrolling is in progress and cannot be interrupted. と怒られてしまい、ちょっとうまく動きませんでした。

I any try, cant pan twice than.


他の手段として、SUPPORT_POINTER_EVENTSをhammerjs側で無効にするという方法を見つけました。

I found other method, disabled SUPPORT POINTER EVENTS.


Angularで2.0.8のhammerjsを使っている場合、以下のようなコードをmain.tsに書くと良いでしょう。

Below code help you, if you use hammerjs v2.0.8 with Angular.


しかし、この手法をとった場合、SVG Elementの移動でtouchmoveによるスクロールの伝播が発生してしまうようです。(HammerConfigうまくやったら回避できたりするのかな?)

but, this method cannot without scroll propagation.

ss


// ...omit

import { HammerGestureConfig, HAMMER_GESTURE_CONFIG } from '@angular/platform-browser';
import * as Hammer from 'hammerjs';

// ...omit

const MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i;
const SUPPORT_TOUCH = 'ontouchstart' in window;
const SUPPORT_ONLY_TOUCH = SUPPORT_TOUCH && MOBILE_REGEX.test(navigator.userAgent);
const inputClass = SUPPORT_ONLY_TOUCH ? Hammer.TouchInput : SUPPORT_TOUCH ? Hammer.TouchMouseInput : Hammer.MouseInput;
class MyHammerConfig extends HammerGestureConfig {
  buildHammer(element: HTMLElement) {
    return new Hammer(element, { inputClass });
  }
}

@NgModule({
  ...omit,
  providers: [{ provide: HAMMER_GESTURE_CONFIG, useClass: MyHammerConfig }]
}]

ref

@walmink Same problem on iOS and same solution. Thank you very much for posting.

I was pleasantly surprised hammer.js even worked on SVG elements when I'm only attaching hammer to the div container but couldn't figure out why pointercancel was being invoked for svg elements when it wasn't if the div contained an image instead.

Not to get too off topic but it's nice to see support being started for this very useful package by @squadette.

See also: https://stackoverflow.com/a/47561100/6535053

I added a script tag in my index.html with delete window.PointerEvent and it... just works? A little frustrated that this issue is so prevalent, and there are no fixes other than these hacks.

Was this page helpful?
0 / 5 - 0 ratings