hammerjs / hammer.js

A javascript library for multi-touch gestures :// You can touch this
http://hammerjs.github.io
MIT License
24.13k stars 2.62k forks source link

Pan event doesn't work correctly with touch-action pan-y in Chrome 55 #1050

Open zuibunan opened 7 years ago

zuibunan commented 7 years ago

Before upgrade to Chrome 55.0.2883.59 beta (64-bit), it works fine but now, when I pulldown in chrome emulator , the panmove event just triggered once or twice, and the event data is not correct

image

this is the right event data when i set touchAction none, but if touchAction is none , the page will not scroll

image

coarse demo http://jsbin.com/tevitozuku/edit?output

BossLevel commented 7 years ago

I have a similar problem - panmove has been working for nearly 2 years, no change to the code and it's stopped. This is Chromium Version 55.0.2883.75 (64-bit). The event appears to trigger correctly at first but the event data I am capturing (var x = event.changedPointers[0].movementX -1;var y = event.changedPointers[0].movementY -1;) appears as 0 values. The event will then trigger with any mouse move (not pan) over the bound area.

It appears to work fine in Firefox 50.0.2 but there my bound Tap events are now passing incorrect x/y info.

glomotion commented 7 years ago

I think i too am having this issue after updating to Chrome to Version 55.0.2883.75.

All pan directions are a problem for me right now. PanStart fires as expected, but the onPan event fires once only, for that entire gesture.

*Note, it only seems to be a problem while touch emulation is running. If you disable "touch emulation" then everything works as expected.

My guess is the chrome team changed something in the way that chrome emulates device touch events...

vnavarrete17 commented 7 years ago

+1 , I have the same problem on pandown/panup and my panend is never triggered . :(

IgorKharkiv commented 7 years ago

+1 , I have the same problem on pandown/panup and my panend is never triggered . :(

+1 This is a very big problem =((

BossLevel commented 7 years ago

Agreed - I'm going to have to pull hammer out of my project if it remains this way.

sitexw commented 7 years ago

Ok, I'm not crazy ^^ I have exactly the same problem. (Except that in my case, I am on the eve of a release in production !)

arjunkathuria commented 7 years ago

looks like chrome team indeed update touch-action options.

they added 'pan-up pan-down pan-left pan-right' with earlier pan-x and pan-y. link here

they might be interfering with the TOUCH_ACTION constants in hammer. link here

These properties force the user to start gestures in one direction before the element will respond. This is similar to the "pull-to-refresh" gesture which only responds when the user gestures downwards on the screen.

This line could be the troubling bit.

cc:- @arschmitz

glomotion commented 7 years ago

So we're also in the unfortunate predicament of being very, very close to a launch. The app concerned is pretty heavily affected by this bug.

Is there any chance at all, we could get some kind of eta on a potential patch? Or even just an indication of how severe this problem really is (and how complicated the fix will need to be) - would be awesome.

I really don't want to have to rip hammer out of the project (hammer rox!) - but may be forced to if this issue remains for much longer. :(

zuibunan commented 7 years ago

@glomotion Chrome Dev for Android have the problem too , not only emulator

glomotion commented 7 years ago

@zuibunan Yup. I'm now finding that too. :( I think it was because the Android devices I was testing on were yet to upgrade to Chrome 55. 55 appears to have since rolled out to all our test devices now - and now they all show this problem.

raghavendra-polanki commented 7 years ago

Any one able to find a solution for this problem?

gargsahil25 commented 7 years ago

Make sure you set the proper touch-action css attribute for your canvas element. Ref: https://developers.google.com/web/updates/2016/10/touch-action My issue got resolved by adding below code in my css .main-canvas { touch-action: none !important; }

rejhgadellaa commented 7 years ago

I've done some digging and testing and it looks like the Chrome's new Pointer Events api has some issues:

That last thing might be the problem if you, like me, attached hammer to an element that has children because the event gets cancelled if you start the touch on a child element (it fires pointerdown, pointermove and then pointercancel).

I've created tickets for the issues above on the chromium tracker so you can star those: https://goo.gl/esp1S5 and https://goo.gl/LesGXI

rejhgadellaa commented 7 years ago

As an addition to @gargsahil25 solution: make sure the touch-action property is set to all child elements as well: .main-canvas, .main-canvas * { touch-action: none; }

I recommend not using the !important because you may want to override that property for one or more child elements, like.. .main-canvas .cant-touch-this { touch-action: auto; }

Also, using !important is not a good idea in general and should only be used when needed. Or maybe unless gargsahil25 has a very good reason for using that, of course :)

(PS: the above solution is a complete hack/workaround for a bug. the bug needs fixing, this solution needs to go :P)

antipod commented 7 years ago

Thanks @gargsahil25, it worked semi-ok. I have a couple of carousels on the mobile website and this at least made it pan i y-direction as it did before. The only problem is that before Chrome 55 rolled out, I could put my finger on the carousel and scroll up and down on the page, whereas now nothing happens.

Maybe that can be solved with hammer.js itself. If anyone have a clue about how to fix that, I'm listening.

rejhgadellaa commented 7 years ago

@antipod You want horizontal carousel but have vertical scroll work? `touch-action: pan-y;

See hammerjs docs: http://hammerjs.github.io/touch-action/

And: https://github.com/hammerjs/hammer.js/wiki/How-to-fix-Chrome-35--and-IE10--scrolling-(touch-action)`

antipod commented 7 years ago

@rejhgadellaa I did try that but it doesn't work completely. I can pan in the x direction, and it will allow me to scroll on the page. BUT, hammer.js registers that vertical scroll as a panleft / panright on the carousel and it bugs like it did before, after Chrome updated to 55. Though it only registers it once. Still, this offsets the carousel. Might be a solution there somewhere... I'll find it.

rejhgadellaa commented 7 years ago

@antipod did you also attach touch-action to all the child elements? see my comment above. I have an implementation like that (with pan-y) and it works for me.

BossLevel commented 7 years ago

I'm afraid that touch-action on my affected elements didn't solve any of them so sadly it appears to be an incomplete hack.

runspired commented 7 years ago

cc @arschmitz I think this is a little out of my knowledge domain at the moment, if there's a new spec to read I'll read it and a link to new APIs is appreciated but sounds like this may also require some spelunking on how the new stuff works?

hanwiz commented 7 years ago

When I use v2.04 or the latest version v2.08 Nov, this doesn't allow me to pan or swipe in Chrome 55. However, with v2.08 Apr, distributed version irons out all the bugs I got. So someone like me should use the distributed version first before trying anything else.

Oops, actually without "touch-action: none", it doesn't work in the Chrome debugging mode (touch emulation).

So, you should use both the distributed version v2.08 Apr and touch-action: none.

BossLevel commented 7 years ago

Again, using the Apr distributed version (I'm assuming by that you mean the .min.js file) === no dice (I was using that version all along).

Noting that the Hammer home page works fine and demos the functionality that I previously had working with panmove, I had a look at the code. I see there that you use the event.deltaX/Y values where I had been using the event.changedPointers[0].movementX/Y values. However adapting my code to use these did not result in a fix as e.g. changing direction mid-pan is not reflected by these values (as they are deltas).

@runspired & @arschmitz I really apologise for telling you guys what to do, but it strikes me that if the developers of the world's most popular browser decide to alter their internal API and that impinges directly on your maintained project's functionality, then you're going to have to bite the bullet and re-factor. Or be maintainers of an increasingly out-of-date project.

BossLevel commented 7 years ago

And if it's any consolation (and I know it isn't) I've had Google do this to me on multiple occasions also :(

hanwiz commented 7 years ago

@BossLevel Can you adapt your code something like this? In firefox, two values are the same most of the time except outside the boundaries. Here is my pen http://codepen.io/hanwiz/pen/ObqGEo.

var pY=0;
// listen to events...
mc.on("panleft panright panup pandown tap press", function(ev) {
    myElement.textContent = ev.type +" gesture detected." + ev.changedPointers[0].movementY + " " + (ev.deltaY - pY);
   pY = ev.deltaY;
});
BossLevel commented 7 years ago

@hanwiz - thanks for your time and effort - the good news is that with some slight modifications, your code works :)

2 points to make before documenting said modifications:

1) I had wanted to avoid the use of some "globals" (pY - I know it's not a proper global) as it seemed redundant to have to manage these when Hammer was meant to be doing it for me. However given the ease and speed of implementation utilising these, needs must. 2) FYI, as per my original post, the ev.changedPointers[0].movementX/movementY values are always zero for me, no matter where and how fast I pan.

With that in mind, line 14 of your pen can be reduced to myElement.textContent = ev.type +" gesture detected." + (ev.deltaY - pY);

In addition, after ending a panmove, the next pan jerks initially. To remove this add the additional following binding:

mc.on('panend', function(event) {
            pX = 0;
            pY = 0;
        });

I still think something needs to be done about this issue (or at least have an example/documentation on the site) but that's your business and I once again thank you for your efforts, particularly @hanwiz

hrvojekindl commented 7 years ago

Adding the "touch-action: pan-y" to the affected scrollable element (and it's descendants) solved the issue for me. Here's the SASS class that I used:

.scroll-and-swipe {
  overflow-y: scroll;
  touch-action: pan-y !important;
  -webkit-overflow-scrolling: touch;

  & * {
    touch-action: pan-y !important;
  }
}
glomotion commented 7 years ago

So I'm still having quite a few problems with Hammer. Even the simplest examples of hammer seem to be broken in Chrome (with touch emulation enabled) atm. Also, having padding applied to the hammer bound element also seems to cause issues with offset calcs.

http://codepen.io/creative-lab-sydney/pen/ygyMMO

miksh7 commented 7 years ago

One of the Chromium tickets mentioned above has been fixed https://bugs.chromium.org/p/chromium/issues/detail?id=161464

Would it fix this issue or the Hammer team should still make a fix?

Mathews2115 commented 7 years ago

@antipod - I noticed on chrome (at least in the touch emulation), that when I get the bogus pan event on vertical scroll, the srcEvent.type is pointercancel.

So a quick ugly work around is to set a panning variable to true on pan-start. But only if the srcEvent.type isnt pointercancel, else, set it to false.

Then on your horizontal pan handler, only if panning is true, do your actual stuff. Otherwise, preventDefault. I haven't fully vetted this out yet but it seems to work initially.

Hope this helps!

alexanderwiebe commented 7 years ago

I followed @Mathews2115 steps above and it seems to be working for me. I did not find that the sass rules impacted this.

I made a quick slider with some scrolling to test out in: Three 'full screen' columns with a footer.

http://codepen.io/alexanderwiebe/pen/qRvbOY

For all events I would wrap existing event code with the following statement: if(ev.srcEvent.type !== 'pointercancel'){

Are there any other effects of ignoring pointercancel in other browsers ? I haven't found any in quick testing.

wmertens commented 7 years ago

@Mathews2115 has your workaround been behaving well? Would it be hard to do this in hammerjs itself?

I don't understand how the bogus event comes to be, for me it only happens when I pan across a certain area of my screen…

Mathews2115 commented 7 years ago

@wmertens So far so good on my end.

blackswanny commented 7 years ago

fixed with the same if(ev.srcEvent.type !== 'pointercancel'){ however, 50% of my pans are marked as canceled, which are rejected

Zenfeder commented 7 years ago

@glomotion Thank you very much, it works correctly when i do it as you said.

MattKunze commented 7 years ago

@blackswanny - I think I'm seeing similar behavior where many of the pans aren't being recognized right on Android, I made a project to reproduce the issue and #1118 as a simple fix that improves the case I'm running into at least

web-jenezis commented 7 years ago

Got the same issue yesterday during the work with Hammer.js. Here is the solution:


// by default, it only adds horizontal recognizers
var mc = new Hammer(myElement);

// let the pan gesture support all directions.
// this will block the vertical scrolling on a touch-device while on the element
mc.get('pan').set({ direction: Hammer.DIRECTION_ALL });```
paranoidjk commented 7 years ago

same problem here.

malina-kirn commented 7 years ago

@web-jenezis : your solution worked for me. I saw that the pointercancel event type pointed out by @alexanderwiebe was indeed causing the bad deltaX and deltaY values, but as @blackswanny commented, some interactions were nearly all pointercancel events, so discarding them would occasionally cause the pan to be almost completely unresponsive. Setting the pan direction caused the pointercancel events to stop firing altogether.

blackswanny commented 7 years ago

@web-jenezis @malina-kirn I used Hammer for React and not sure that directions prop is passed to hammer, cause i still see issues with it


 <Hammer
            direction={Hammer.DIRECTION_ALL}
            onPan={this.onPan}
            onPanEnd={this.onPanEnd}
            onPanStart={this.onPanStart}
            onDoubleTap={this.onDoubleTap}
            options={{
                recognizers: {
                    swipe: {
                        enable: false
                    },
                    pinch: {
                        enable: false
                    },
                    press: {
                        enable: false
                    },
                    pan: {
                        enable: true
                    },
                    tap: {
                        enable: true
                    }
                }
            }}>
malina-kirn commented 7 years ago

@blackswanny : as @web-jenezis pointed out, setting the direction attribute blocks vertical scroll on the element. Perhaps you can block native vertical scroll instead of setting direction? This also seems to work for me:

CSS:

.no-scroll {
  overflow: hidden;
}

Add the no-scroll class to the element you're panning. I'd previously tried this on outer elements (blocking scroll on the entire page), but it seems it must be applied directly to the element being panned. I suspect it would have worked if I'd applied the rule to all child elements.

grumpygary commented 6 years ago

add: delete window.PointerEvent; in your index.html PRIOR to loading hammer.js, and it will ignore those events.
You can also set SUPPORT_POINTER_EVENTS = false; (line 384 of v2.0.8) to do the same thing. Ideally the devs would add the ability to turn this off, but so goes the open source dilemma...

ghost commented 6 years ago

@garyskiba

easier solution if you add the

var SUPPORT_POINTER_EVENTS = false; line at the beginning of your file.

It's dirty but at least don't touch the hammer.js itself

blackswanny commented 6 years ago

@fssrepository , according to what i see it's not global var and we can't override it so hammer will pick up another value. But I would rather change this var than override native PointerEvent, cause not like to affect other parts of app

SelfishLucho commented 6 years ago

@garyskiba @fssrepository Both solutions brakes 'press' if u are unfornate as me if u want to use both 'pan' and 'press' with hammerJS globally available

simeyla commented 6 years ago

The actual issue described still persists today - not surprising since hammerjs hasn't been updated.

That is the issue of getting wildly odd numbers like

deltaX: -120 deltaY: -118

As others have mentioned, the solution is to use touchAction: 'none' on the thing you're panning. Unfortunately if you want this 'thing' to be scrollable vertically (as part of normal vertical scrolling behavior) you must remove this touchAction - because the browser locks the scrollability when you have it.

I had cases where I wanted to be informed about the attempt of the user to scroll - i didn't really need a delta. I just needed to know which direction it was.

Turns out you can filter out these bizarre numbers with:

filter(e=> e.center.x != 0 && e.center.y != 0)

That leaves you with a few events like e.deltaX = 2, e.deltaY = -11 but that will let you scroll the page and still get an indication of the users intent.

Remember that when you scroll the whole page what do you really expect delta to be? You're moving the whole viewport after all! This at least filters out that awful janky large seemingly random numbers.

Me 36 hours ago: "I'm so excited to finally have time to play with hammerjs, the leading gestures library - and never have to worry about compatibility ever again" Me now: REDACTED

marcus13371337 commented 6 years ago

On the parent wrapper: touch-event: none

The element inside the wrapper I wanted to be able to scroll vertically: touch-event: pan-y

Solved it for me!

divisey commented 5 years ago

I have tried

if (ev.eventType !== Hammer.INPUT_MOVE) return;

in my handler. this saves me from the trouble.

melnikovau commented 4 years ago

Thanks @divisey

That combo works fine to me:

var mc = new Hammer(kek, {
    recognizers: [
        [Hammer.Pan, { direction: Hammer.DIRECTION_HORIZONTAL, pointers: 1, threshold: 10 }]
    ]
});

mc.on('panright panleft', function(ev) {
    ev.preventDefault();
    if (ev.eventType !== Hammer.INPUT_MOVE) return;
    if (ev.pointerType == 'touch' && (Math.abs(ev.deltaX) <= Math.abs(ev.deltaY)+5)) return;
    if (ev.srcEvent.type !== 'pointercancel') {
        ...
        your code
        ...
    }
});
elena-cz commented 4 years ago

I was also getting a lot of premature pointercancel events while panning on Chrome. This ended up working for me:

Adding the touch-event: pan-y property to the child div inside the parent Hammer container. So for the hammer container, I left the default settings (which applies "touch-event: pan-y"), and didn't have to add "touch-events: none" anywhere.