Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

In Android browsers, panning on child of SVG breaks when dragged more than a few pixels #1182

Open
walmink opened this issue Jun 15, 2018 · 7 comments

Comments

@walmink
Copy link

walmink commented Jun 15, 2018

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.

@walmink
Copy link
Author

walmink commented Jun 17, 2018

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.

@Hoody123
Copy link

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!

@ver-1000000
Copy link

Very help full me, thanks!


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

@ver-1000000
Copy link

ver-1000000 commented Jul 14, 2019

いろいろテストしていたところ、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

@FrankReh
Copy link

FrankReh commented Jan 7, 2020

@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.

@adamtaylor13
Copy link

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.

@rupertlssmith
Copy link

rupertlssmith commented Nov 24, 2022

Just spent some time with a closely related issue - pointercancel happening when I don't want it to. Setting "user-select: none" can also help with this.

This can happen when drag starts inside one div and ends somewhere else. In my case drag starting in an SVG, but ending outside of it on another div. This can form a browser selection, even if it is not shown because no actual text was selected. Setting "user-select: none" on the SVG prevented this from happening, and I did not get unwanted pointercancel events any more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants