0

I'm facing a strange problem that I think leaves HammerJS internal event loop with a stuck event that ruins subsequent detections.

This only happens on Internet Explorer Edge on a Touch Device with PointerEvents.

Basically, when using HammerJS for a PAN event (panstart -> panmove -> panend), and you cross the current frame boundary (for example, into an IFRAME, or just outside the browser window) AND you release your finger there, then HammerJS never receives the CANCEL event and the session kind of stays stuck.

From then on, all gestures are reported incorrectly, with one more finger ('pointer') than you're using: For example, it will report a PINCH or ROTATE (2 pointers) just tapping (1 pointer) and so on.

I haven't found a way to reset the Hammer Manager once it enters this ghost state. This breaks my app.

I've prepared a Fiddle with a full working example. Please execute it under a Windows/Touch device !

enter image description here

https://jsfiddle.net/28cxrupv/5/

I'd like to know, either how to detect the out-of-bounds event, or just how could I manually reset the Hammer Manager instance if I am able to detect myself by other means that there are stuck events.

UPDATE

I've found in my investigations that the problem is at the lowest level in HammerJS: the PointerEvents handler has an array of detected pointers this.store and there's the stuck event with an old timestamp.

1 Answer 1

0

I've found a way to patch Hammer.JS so it can detect stuck pointers. I don't know if this is wrong, but apparently it works!

On HammerJS PointerEvents handler, there's an array this.store that keeps all current pointer events. It's there where, when we pan out of the window and release the touch, a stuck event is kept forever.

Clearing this array causes Hammer to go back to normal again.

I just added a condition where, if we are processing a Primary touch (start of a gesture?), and the store is not empty, it clears the store automatically.

How it works is, on the next interaction with the stuck hammer instance, the internal store gets reset and the gesture is interpreted properly.

On Hammer.js 2.0.6, around line 885

        /**
         * handle mouse events
         * @param {Object} ev
         */
        handler: function PEhandler(ev) {
            var store = this.store;
            var removePointer = false;

            var eventTypeNormalized = ev.type.toLowerCase().replace('ms', '');
            var eventType = POINTER_INPUT_MAP[eventTypeNormalized];
            var pointerType = IE10_POINTER_TYPE_ENUM[ev.pointerType] || ev.pointerType;

            var isTouch = (pointerType == INPUT_TYPE_TOUCH);

            // get index of the event in the store
            var storeIndex = inArray(store, ev.pointerId, 'pointerId');

            // start and mouse must be down
            if (eventType & INPUT_START && (ev.button === 0 || isTouch)) {

                // NEW CONDITION: Check the store is empty on a new gesture
                // http://stackoverflow.com/questions/35618107/cross-frame-events-on-ie-edge-break-hammerjs-v2

                if (ev.isPrimary && store.length) {
                    window.console.warn ("Store should be 0 on a primary touch! Clearing Stuck Event!");
                    this.reset();
                }

                if (storeIndex < 0) {
                    store.push(ev);
                    storeIndex = store.length - 1;
                }
            } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
                removePointer = true;
            }

            // it not found, so the pointer hasn't been down (so it's probably a hover)
            if (storeIndex < 0) {
                return;
            }

            // update the event in the store
            store[storeIndex] = ev;

            this.callback(this.manager, eventType, {
                pointers: store,
                changedPointers: [ev],
                pointerType: pointerType,
                srcEvent: ev
            });

            if (removePointer) {
                // remove from the store
                store.splice(storeIndex, 1);
            }
        }
    });

and also I define the function "reset":

        /**
         * Reset internal state
         */

        reset: function() {
            this.store = (this.manager.session.pointerEvents = []);
        },

Not the answer you're looking for? Browse other questions tagged or ask your own question.