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

Touch: Use touch-action and passive event listeners if supported #309

Closed
wants to merge 1 commit into
base: master
from

Conversation

Projects
None yet
4 participants
@bethge
Member

bethge commented Aug 25, 2016

This PR aims at using native touch-action and passive event listeners (#278), if the browser supports them.

If touch-action is supported, we can apply the matching touch-action style to the elements with the touch-action attribute. We would also no longer need to preventDefault() any touch events, since we only had to do it to mimic the behaviour of touch-action.

Iiuc, with touch-action and passive event listener support we can use the code originally meant for touch-action-delay (https://github.com/jquery/PEP/blob/master/src/touch.js#L17-L22). In https://crbug.com/347272 touch-action-delay has been dropped in favour of passive event listeners.

With touch-action and passive event listeners supported, the touch event listener can attach to document, since performance should no longer be an issue (not yet tested), and installer.js with the MutationObserver won't be needed anymore. We still need to be able to detect if at any point during a touch gesture some default user agent behaviour takes over (I hope I got this part correct). Once default UA behaviour takes over, we need to fire a pointercancel and ignore subsequent touchmove events.
To detect default UA behaviour, I check if touchmove is cancelable. If it is cancelable, no default UA behaviour is taking place and a pointermove should fire. If it is not cancelable, some default UA behaviour is taking over (passive event listeners disallow canceling default UA behaviour, setting cancelable to false).

Using native touch-action will change some of the behaviour of PEP.
E.g. the behaviour of nested elements with different touch-action properties will be different. PEP allows an element with touch-action: auto to scroll, even if it is a child of an element with touch-action: none. Using native touch-action no scrolling would be possible, see: http://bethge.github.io/touch-action/pep-touch-action.html
Also, with native touch action support, an element with touch-action: auto may still fire pointer events, e.g. if the user "scrolls against the wall".

I haven't checked any corner cases at this point. Just want to make sure I got in principle the expected behaviour and whether we are o.k. with different behaviours across browsers.

@bethge

This comment has been minimized.

Show comment
Hide comment
@bethge

bethge Aug 27, 2016

Member

I looked some more into the relation between touch-action and TouchEvent.cancelable in Chrome 54.0.2839.0 with Pointer Events enabled.

I made a little example: http://bethge.github.io/touch-action/wall-scroll.html with passive event listeners:

  • touch-action: auto box and touch-action: auto with scrollable content
    • all touchmove are cancelable == false
    • all drags fire pointercancel
  • touch-action: pan-x box
    • all touchmove are cancelable == true
    • x-axis drags fire pointercancel
  • touch-action: pan-x with scrollable content box
    • only touchmove that scroll the content are cancelable == false, all others are true
    • x-axis drags fire pointercancel
  • touch-action: none box
    • all touchmove are cancelable == true
    • no drags fire pointercancel

Iiuc, Chrome always fires pointercancel if touch-action would allow UA behaviour to take over. Should pointercancel fire if UA behaviour may take over, or if it does take over?

Also, touch-action: auto has always touchmove.cancelable == false, but touch-action: pan-x only if UA behaviour takes over. This seems a bit inconsistent, am I missing a part of the bigger picture?

Member

bethge commented Aug 27, 2016

I looked some more into the relation between touch-action and TouchEvent.cancelable in Chrome 54.0.2839.0 with Pointer Events enabled.

I made a little example: http://bethge.github.io/touch-action/wall-scroll.html with passive event listeners:

  • touch-action: auto box and touch-action: auto with scrollable content
    • all touchmove are cancelable == false
    • all drags fire pointercancel
  • touch-action: pan-x box
    • all touchmove are cancelable == true
    • x-axis drags fire pointercancel
  • touch-action: pan-x with scrollable content box
    • only touchmove that scroll the content are cancelable == false, all others are true
    • x-axis drags fire pointercancel
  • touch-action: none box
    • all touchmove are cancelable == true
    • no drags fire pointercancel

Iiuc, Chrome always fires pointercancel if touch-action would allow UA behaviour to take over. Should pointercancel fire if UA behaviour may take over, or if it does take over?

Also, touch-action: auto has always touchmove.cancelable == false, but touch-action: pan-x only if UA behaviour takes over. This seems a bit inconsistent, am I missing a part of the bigger picture?

@scottgonzalez

This comment has been minimized.

Show comment
Hide comment
@scottgonzalez

scottgonzalez Sep 1, 2016

Member

@RByers Can you comment on the TouchEvent.cancelable behavior in Chrome described above?

Member

scottgonzalez commented Sep 1, 2016

@RByers Can you comment on the TouchEvent.cancelable behavior in Chrome described above?

@RByers

This comment has been minimized.

Show comment
Hide comment
@RByers

RByers Sep 1, 2016

Also, with native touch action support, an element with touch-action: auto may still fire pointer events, e.g. if the user "scrolls against the wall".

You're saying that trying to scroll an element that's already at it's scroll extent will not fire pointercancel? I don't think that should be the case. AFAIK it's a design goal in both Chrome and Edge that the pointer event stream doesn't change depending on the scroll offset or scroll height of the scroller being touched (in contrast to Chrome's touch event behavior). You can see this in action here.

Iiuc, Chrome always fires pointercancel if touch-action would allow UA behaviour to take over. Should pointercancel fire if UA behaviour may take over, or if it does take over?

That is correct. Think of it this way, there are two separate stages: generating a gesture event and that gesture event having an action. touch-action determines what gesture events may be generated, while TouchEvent.cancelable is set to false when we know the last gesture event actually had some action (because we don't want the user to feel a jank between scrolls that actually did something). The TouchEvent behavior of chromium is described in some detail here. I'm sorry it's so complicated, I regret some of this now that we have other simpler designs (pointer events, scroll customization) getting closer to shipping. Perhaps some day we'll undo some of the fanciness in chromium and more closely match Safari.

Also, touch-action: auto has always touchmove.cancelable == false, but touch-action: pan-x only if UA behaviour takes over. This seems a bit inconsistent, am I missing a part of the bigger picture?

Yes this is correct and it is indeed inconsistent. It boils down to a limitation we have in chromium that effectively means that touch-action acts as a non-passive touch listener at the moment (we're collecting metrics on this and plan a major engineering investment to lift the limitation when touch-action usage is high enough to result in some real wins). But we could perhaps put some hack in place before that to force cancelable=false here if it makes things easier in scenarios like your own.

A TouchEvent is cancelable in chromium unless:

  1. A scroll or zoom is currently active (i.e. the last gesture event had an effect), or
  2. All of the event listeners are passive, or
  3. We have forced listeners to be treated as passive by default as a performance intervention (not yet shipped) and none have explicitly opted-out with passive:false.

So you can't infer too much about the exact scenario your in from the cancelable bit - it simply means that the UA (for possibly UA-specific reasons) will allow preventDefault on the event.

If I understand your issue correctly, you're just trying to decide when to fire the pointercancel event, right? To get this just right I don't think you want to rely on cancelable. Instead when the first touchmove event occurs in a touch sequence (after 1 or more touchstart events) you should try to determine if the gesture the user is starting is allowed by touch-action. If it is, then send a pointercancel and suppress all pointer events until the next touch sequence.

Computing whether a gesture is allowed by touch-action is non-trivial. To handle all cases (where a developer may have arbitrary complex style rules) you need to implement the touch-action processing model in the spec where you walk up the DOM, getting the computed style and intersecting all the touch-action values. Perhaps something simpler would be good enough though?

Also note that while Safari now supports passive touch listeners and the touch-action property, they don't actually support any touch-action values other than manipulation and auto. So you have to explicitly feature-detect for the value you want to use. With Chrome being (hopefully) just months away from shipping pointer events support, I'm not sure whether you'll have many users left that have native touch-action but not PointerEvents...

RByers commented Sep 1, 2016

Also, with native touch action support, an element with touch-action: auto may still fire pointer events, e.g. if the user "scrolls against the wall".

You're saying that trying to scroll an element that's already at it's scroll extent will not fire pointercancel? I don't think that should be the case. AFAIK it's a design goal in both Chrome and Edge that the pointer event stream doesn't change depending on the scroll offset or scroll height of the scroller being touched (in contrast to Chrome's touch event behavior). You can see this in action here.

Iiuc, Chrome always fires pointercancel if touch-action would allow UA behaviour to take over. Should pointercancel fire if UA behaviour may take over, or if it does take over?

That is correct. Think of it this way, there are two separate stages: generating a gesture event and that gesture event having an action. touch-action determines what gesture events may be generated, while TouchEvent.cancelable is set to false when we know the last gesture event actually had some action (because we don't want the user to feel a jank between scrolls that actually did something). The TouchEvent behavior of chromium is described in some detail here. I'm sorry it's so complicated, I regret some of this now that we have other simpler designs (pointer events, scroll customization) getting closer to shipping. Perhaps some day we'll undo some of the fanciness in chromium and more closely match Safari.

Also, touch-action: auto has always touchmove.cancelable == false, but touch-action: pan-x only if UA behaviour takes over. This seems a bit inconsistent, am I missing a part of the bigger picture?

Yes this is correct and it is indeed inconsistent. It boils down to a limitation we have in chromium that effectively means that touch-action acts as a non-passive touch listener at the moment (we're collecting metrics on this and plan a major engineering investment to lift the limitation when touch-action usage is high enough to result in some real wins). But we could perhaps put some hack in place before that to force cancelable=false here if it makes things easier in scenarios like your own.

A TouchEvent is cancelable in chromium unless:

  1. A scroll or zoom is currently active (i.e. the last gesture event had an effect), or
  2. All of the event listeners are passive, or
  3. We have forced listeners to be treated as passive by default as a performance intervention (not yet shipped) and none have explicitly opted-out with passive:false.

So you can't infer too much about the exact scenario your in from the cancelable bit - it simply means that the UA (for possibly UA-specific reasons) will allow preventDefault on the event.

If I understand your issue correctly, you're just trying to decide when to fire the pointercancel event, right? To get this just right I don't think you want to rely on cancelable. Instead when the first touchmove event occurs in a touch sequence (after 1 or more touchstart events) you should try to determine if the gesture the user is starting is allowed by touch-action. If it is, then send a pointercancel and suppress all pointer events until the next touch sequence.

Computing whether a gesture is allowed by touch-action is non-trivial. To handle all cases (where a developer may have arbitrary complex style rules) you need to implement the touch-action processing model in the spec where you walk up the DOM, getting the computed style and intersecting all the touch-action values. Perhaps something simpler would be good enough though?

Also note that while Safari now supports passive touch listeners and the touch-action property, they don't actually support any touch-action values other than manipulation and auto. So you have to explicitly feature-detect for the value you want to use. With Chrome being (hopefully) just months away from shipping pointer events support, I'm not sure whether you'll have many users left that have native touch-action but not PointerEvents...

@bethge

This comment has been minimized.

Show comment
Hide comment
@bethge

bethge Sep 8, 2016

Member

As discussed in today's PEP meeting, we'll not further pursue using passive event listeners in combination with touch-action, if the browser supports it.

Chrome is getting ever closer to supporting pointer events natively, and no other browser supports or in the near future plans to support passive event listeners as well as touch-action.

Member

bethge commented Sep 8, 2016

As discussed in today's PEP meeting, we'll not further pursue using passive event listeners in combination with touch-action, if the browser supports it.

Chrome is getting ever closer to supporting pointer events natively, and no other browser supports or in the near future plans to support passive event listeners as well as touch-action.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment