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

Imperative API to request suppression of panning and zooming #216

Open
RByers opened this issue Sep 8, 2017 · 11 comments
Open

Imperative API to request suppression of panning and zooming #216

RByers opened this issue Sep 8, 2017 · 11 comments
Labels

Comments

@RByers
Copy link
Contributor

RByers commented Sep 8, 2017

The only way to suppress panning and zooming with Pointer Events (or passive Touch Events) is via the declarative touch-action API. This works great in most cases, but occasionally there are special cases (like #178) which can't be solved with touch-action. We certainly don't want to re-introduce all the performance problems of TouchEvent.preventDefault, but chatting with @mdittmer this morning we realized that there are scenarios (#178 in particular) where you can get the best of both worlds by having an asychronous API. Eg:

interface PointerEvent {
  Promise<bool> tryDisableTouchActions();
}

tryDisableTouchActions would resolve to true if panning and zooming have been suppressed for the duration of the associated pointer, and false otherwise (pointer not active or panning/zooming already started). When successful, this API causes the browser to behave exactly as if touch-action: none had been set on the element before the pointerdown occurred.

This wouldn't be a replacement for touch-action. For example, there would be no non-racy way to use it for a carousel scenarios - those still must use touch-action: pan-y. But for anything involving a timer, this solution should be no more racy than a touch-action-based alternative.

@RByers RByers changed the title Imperative API to prevent panning and zooming Imperative API to request suppression of panning and zooming Sep 8, 2017
@flackr
Copy link
Contributor

flackr commented Sep 8, 2017

Would having something like preventDefault on a passive event return a Promise be a reasonable workaround here? I.e. until the panning has started calling preventDefault on a passive touchmove event listener event will resolve the promise and prevent panning?

@RByers
Copy link
Contributor Author

RByers commented Sep 8, 2017

Note that this API would obviously be required to work beyond long after the associated PointerEvent had been dispatched. I imagine developers would save the "current pointer" from a pointerdown listener, and invoke tryDisableTouchActions on it in a timer callback. One downside to this is that it could lead to developers keeping PointerEvent instances alive longer than necessary (eg. not clearing on pointerup), which could in turn keep an otherwise removed section of the DOM alive in RAM (due to the reference from Event.target).

It's tempting to design a more stateless API (more like Element.setPointerCapture which takes a pointerId) but that would have the downside of being racey in scenarios where a user quickly flicks multiple times on different elements. Really I think we'd want an API that is somehow tightly coupled to a specific active pointer sequence, not just a pointerId.

@staktrace @smaug---- @patrickkettner @patrickhlauke @dtapuska @NavidZ @mustaqahmed thoughts?

@RByers
Copy link
Contributor Author

RByers commented Sep 8, 2017

Would having something like preventDefault on a passive event return a Promise be a reasonable workaround here? I.e. until the panning has started calling preventDefault on a passive touchmove event listener event will resolve the promise and prevent panning?

That's conceptually the idea. We can't change the return value of Event.preventDefault so we need some new API. And panning/zooming is intentionally not the "default action" of a pointer event, so to solve this for pointer events it shouldn't be associated with preventDefault.

@flackr
Copy link
Contributor

flackr commented Sep 8, 2017

I like the idea, I think it works well for these scenarios.

Is there any chance we may also want to allow setting another touch-action rather than just consuming the active event stream?

@dtapuska
Copy link

dtapuska commented Sep 8, 2017

Isn't the Promise really just a proxy for allowing a delayed touch-action to be set? I still don't like the unpredictability of these approaches. I'd prefer a css driven approach where we indicated that say a touch-action: none is applied after x milliseconds if the gesture hasn't started. That is an exact definition of the problem we are trying to solve.

@mustaqahmed
Copy link
Member

I also think a CSS based approach is better. But adding millisecond-level granularity seems too much because we really don't want to allow an arbitrary delay for hold-drag or anything else, right?

The unless-long-press idea proposed in #178 seems cleaner from that perspective: there is only a single (and "established") delay threshold to worry about.

If we go for a Promise-based approach, the involved delay should be obvious in the API IMO. In particular, tryDisableTouchActions could be misleading because the name seems to suggest "disable now if possible" (as if the Promise would complete immediately---rejected if panning/zooming has started already, resolved otherwise). Does disableTouchActionOnLongPress sound better?

@flackr
Copy link
Contributor

flackr commented Sep 8, 2017

The issue with either specifying a timeout or having a fixed timeout is the lack of flexibility. It may turn out not to matter, but the developer may want a custom timeout - or may even want to make the decision based on the events seen so far.

I don't think a promise makes any guarantee about timing, just as a request promise will come later. The advantage is that you can have complex logic deciding when you want to take over a touch stream that hasn't turned into a gesture yet.

@mustaqahmed
Copy link
Member

Since we are discussing imperative APIs here, a non-Promise-based solution could be considered: after the call event.disableTouchActionOnHold(), the browser would fire a touchactioncancel event with the same pointerId.

@mdittmer
Copy link

mdittmer commented Sep 8, 2017

I was actually thinking of this as being less constrained than @RByers suggests. If I understand correctly, Rick suggested that the promise resolves true iff pan/zoom have not been active since the touch gesture began. What I had in mind was ... iff pan/zoom is not currently in progress. That seems like it would be useful for more gestures than just long-press (which was the example use case we had been discussing).

@RByers
Copy link
Contributor Author

RByers commented Sep 8, 2017

If we go for a Promise-based approach, the involved delay should be obvious in the API IMO. In particular, tryDisableTouchActions could be misleading because the name seems to suggest "disable now if possible" (as if the Promise would complete immediately---rejected if panning/zooming has started already, resolved otherwise). Does disableTouchActionOnLongPress sound better?

I'm actually proposing it DOES disable immediately if possible. In practice it should resolve very quickly (in chromium it's just an async mojo call to the browser process, so latency really depends only on the availability of the main thread when the call completes - should often be sub-1ms).

That's what makes this approach more flexible - the app can decide itself when and under what conditions it wants to try to suppress scrolling (eg. maybe it's when three fingers go down, or some other atypical gesture).

Isn't the Promise really just a proxy for allowing a delayed touch-action to be set? I still don't like the unpredictability of these approaches. I'd prefer a css driven approach where we indicated that say a touch-action: none is applied after x milliseconds if the gesture hasn't started. That is an exact definition of the problem we are trying to solve.

It's indeed more predictable to do it entirely declaratively. Eg. if the main thread has 200ms of jank, then what's supposed to be a 1s timeout could actually behave like a 1.2s timeout. If we can agree on a single model (exact timeout value, etc.) and can't come up with any other realistic use case, then I agree that #178 is probably the better solution. But I'm worried there's maybe a long-tail of problems here. Maybe we'd say the right completely general answer there is explaining touch-action in terms of an AnimationWorklet? And so we shouldn't try to provide a concept that sits awkwardly below high-level CSS concepts but above low-level input primitives?

Is there any chance we may also want to allow setting another touch-action rather than just consuming the active event stream?

Yeah I thought about that too - maybe even a 'requestUpdateTouchAction()' API that just asks that the CSS style be re-evaluated for the in-progress gesture. I couldn't come up with any realistic reason that additional flexibility was likely to be valuable, and it seemed more complicated to me (a complex JS/CSS interaction, instead of what is conceptually a JS API that's exposes a piece of the primitives underlying the higher-level CSS concepts). Dunno though.

@RByers
Copy link
Contributor Author

RByers commented Sep 8, 2017

I was actually thinking of this as being less constrained than @RByers suggests. If I understand correctly, Rick suggested that the promise resolves true iff pan/zoom have not been active since the touch gesture began. What I had in mind was ... iff pan/zoom is not currently in progress. That seems like it would be useful for more gestures than just long-press (which was the example use case we had been discussing).

What do you mean by "not currently in progress"? You mean maybe the user started moving their finger, then held still for awhile? I don't think there would really be a well-defined concept there - once a pan or zoom has started at all, there's likely to always be a little bit of finger movement that continues to affect the position (even if the user thinks they're holding their finger still). Of course we could have an API to asynchronously interrupt an active pan/zoom, but I think that's a different story with different use cases and downsides.

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

No branches or pull requests

6 participants