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

Conflict when listening for both pinch and twofingerpan #6

Open
igorski opened this issue Jan 27, 2022 · 7 comments
Open

Conflict when listening for both pinch and twofingerpan #6

igorski opened this issue Jan 27, 2022 · 7 comments

Comments

@igorski
Copy link

igorski commented Jan 27, 2022

Hi there,

first of all fantastic work on the library as its a nice up-to-date variant of a certain popular library.

While I find the implementation of pinch much more accurate than HammerJS provides, I struggle to combine a pinch and twofingerpan listener on the same element. Basically the pan handler is conflicting with the pinch making it impossible to zoom, or vice versa. In HammerJS there was the concept of recognizeWith which would give a PointerListener awareness of the other basically managing their priorities.

Is there something similar available or is my approach a little too naive ? My current code is:

const listener = new Contact.PointerListener( element, {
    supportedGestures: [ Contact.Pinch, Contact.TwoFingerPan ]
});
element.addEventListener( "pinch", event => {
    if ( this.panOrigin ) {
        return; // attempt to not pinch while panning
    }
    if (!this.orgZoomLevel) {
        this.orgZoomLevel = this.zoomLevel; // zoomLevel is a local reactive property (numerical)
    }
    const value = Math.max( MIN_ZOOM, Math.min( MAX_ZOOM, this.orgZoomLevel * event.detail.global.scale ));
    this.zoomLevel = value; // updates reactive property which updates DOM
});
element.addEventListener( "pinchend", () => {
    this.orgZoomLevel = null;
});
element.addEventListener( "twofingerpan", () => {
    if (this.orgZoomLevel) {
        return; // attempt to not pan while zooming
    }
    if ( !this.panOrigin ) {
        this.panOrigin = { ...viewport }; // is bounding box of coordinates (left, top, width, height)
    }
    const { deltaX, deltaY } = event.detail.global;
    this.panViewport( this.panOrigin.left - deltaX, this.panOrigin.top - deltaY ); // updates reactive property which updates DOM
});
element.addEventListener( "twofingerpanend", () => {
    this.panOrigin = null;
});

Attaching either the pinch or twofingerpan listeners separately from each other works fine, when adding them both, twofingerpan becomes dominant.

@igorski igorski changed the title Conflicting when listening for both pinch and twofingerpan Conflict when listening for both pinch and twofingerpan Jan 27, 2022
@biodiv
Copy link
Owner

biodiv commented Jan 28, 2022

Thanks for reporting the issue and thank you for providing code.

It should be possible to simultaneously use pinch and twofingerpan with one PointerListener instance. contactjs by default runs all recognizers when a PointerEvent occurs, and the successful recognition of one should not disable another listener.

I am not yet exactly sure yet what happens in your case. I understand it as follows:

  • you want to use both twofingerpan and pinch with one PointerListener
  • if a pinch has been detected, pan should become impossible. No twofingerpan during pinch, and vice versa.
  • if you try to implement the above, you can only use twofingerpan. pinch is always blocked.

I this is the case, I assume that twofingerpan is always recognized first ans then leads to blocking pinch with the return statement in your code.

Important: Should the user be able to to first pinch and then twofingerpan while staying on the surface with both fingers (and vice versa)? Or should he have to remove his fingers from the surface if he wants to pinch after panning (and vice versa)?

@biodiv
Copy link
Owner

biodiv commented Jan 28, 2022

For illustration I created images for two input scenarios.

Scenario 1: Pinch while Twofingerpan

pinchpan

  • both pinch and twofingerpan events should be fired at the same time.

Scenario 2: Twofingerpan - Pinch - Twofingerpan, fingers never removed

pinchpan_consecutive

  • first vector: only twofingerpan should be fired
  • second vector: only pinch should be fired, this would require a twofingerpanend
  • third vector : only twofingerpan should be fired. This would require a second twofingerpanstart. Alternatively, an "idle" state could be implemented.

Is the described the expected behaviour?

@igorski
Copy link
Author

igorski commented Jan 28, 2022

Hi there,

thanks for your swift reply, as for your questions:

A you want to use both twofingerpan and pinch with one PointerListener
B if a pinch has been detected, pan should become impossible. No twofingerpan during pinch, and vice versa.
C if you try to implement the above, you can only use twofingerpan. pinch is always blocked.

A and B are correct. C isn't the case, pinching does work and so does panning. It's just that they don't trigger entirely according to expectation.

If I interpret your illustration and vectors correctly you are describing the expected behaviour. Just for the sake of clarity, I have created a codepen to illustrate the example. I have simplified the code I added above a little. I'm using Vue in the example, but the bulk of the logic is in the created() hook. Vue is basically computing the changes to the offset and zoom to update the style of the image DOM element.

At certain times, the touch listeners behave very well, but at others the issue becomes apparent, for instance when executing what feels like an obvious pinch which then actually triggers a doublepan. At others, the other way around. Sometimes both happen at the same time (for instance doublepan in a small circular motion would both expand/shrink the image during pan). I have reproduced this on both Pixel 3 (Chrome on Android) and iPad Air (Safari)

@biodiv
Copy link
Owner

biodiv commented Jan 31, 2022

I implemented the possibility to block gestures in the latest build for testing, see docs.

However, scenario 2 as illustrated above will not work this way yet. Example:

var pinch = new Pinch(domElement);
var twofingepran = new TwoFingerPan(domElement);

twofingerpan.block(pinch);
pinch.block(twofingerpan);

If you want to perform a pinch after a twofingerpan without removing the fingers from the surface WITHOUT allowing both pinch and twofingerpan at the same tine, twofingerpan has to be invalidated on the fly before a pinch can be recognized. I am still thinking about the best way to implement this case. I made a few tests and got a lot of "twofingerpanstart" and "twofingerpanend" events. Although this reflected the input I did with my fingers, I am not sure yet if this is the best way to do it.

I also played around with your codepen. I can reproduce what you described when moving both fingers in "parallel circles". I could not reproduce a twofingerpan that felt like a pinch except when being on MAX_ZOOM or MIN_ZOOM. When I maxed out the zoom level and do a pinch, the pinch gets intentionally ignored and the picture moves a little according to the pan input I unintentionally make.

Finally, I found out that twofingerpan gets triggered if I only move one finger and keep the other one on the same spot, which I think is not what users are accustomed to. I will restrict twofingerpan to a parallel movement of 2 fingers.

@igorski
Copy link
Author

igorski commented Feb 2, 2022

Verified the block()-interface to have resolved the issue!

If you want to perform a pinch after a twofingerpan without removing the fingers from the surface WITHOUT allowing both pinch and twofingerpan at the same tine, twofingerpan has to be invalidated on the fly before a pinch can be recognized

This doesn't sound necessarily like a problem because the fingers used for pinching and two finger panning tend to be different (though I understand when comparing the pointer vectors, the exact fingers representing the pointers don't matter).

As far as I'm concerned the original issue has been resolved, feel free to close this issue (not sure if you want to treat the issue quoted above in the scope of this issue, or handle it separetely, if at all).

@paul-mediakitchen
Copy link

Question about the block functionality.
If I have blocked pinch when a pan is taking place, after the panend, if the user has not removed their fingers from the dom element, will a pinch still be detected immediately afterwards.
When doing a two finger pan, I am getting pinching action taking place at the same time I am assuming due to my two fingers moving a bit when panning across the screen. Ideally I would like one gesture only at a time to simply things otherwise the dom elements are moving erratically.

@biodiv
Copy link
Owner

biodiv commented May 21, 2022

If I have blocked pinch when a pan is taking place, after the panend, if the user has not removed their fingers from the dom element, will a pinch still be detected immediately afterwards.

This is scenario 2, as illustrated above. The current block interface only affects scenario 1, where two gestures are simultaneously performed (if I recall it correctly).

When doing a two finger pan, I am getting pinching action taking place at the same time I am assuming due to my two fingers moving a bit when panning across the screen.

There is a setting for the Pinch gesture:

Pinch.initialMinMaxParameters["distanceChange"] = [5, null]

Which states, that there is a (initial) 5 pixel tolerance before a pinch is being recognized, resulting in a pinchstart event. There is no maxparameter for pinch, so it is set to null.

This means the following: the user touches the surface with two fingers. Those fingers have an initial distance between each other. When this distance changes more than 5 pixels, this is interpreted as a pinch. After that, pinch becomes active, and all changes in distance will fire the pinch event. If this setting is too sensitive, you can adjust it. This is an advanced setting which might result in strange behaviors, but I should cover this in the documentation.

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

3 participants