Search code examples
javascriptmobilescrolltouchtouch-event

fighting touch events on mobile devices, highjacking normal scroll


I went through the code several times and I cannot find the reason it fails on touch based devices:

    /**
     * Initialize touch event listener.
     *
     * @returns {Plugin}
     */
    touch: function () {
        var self = this;

        this._$body.bind('touchstart', function (event) {
            var startEvent = event.originalEvent.touches[0];

            event.preventDefault();
            self._$body.bind('touchmove', function (event) {
                var moveEvent = event.originalEvent.touches[0];
                var diff = { x: startEvent.clientX - moveEvent.clientX, y: startEvent.clientY - moveEvent.clientY };
                var nextStep;
                event.preventDefault();
                if ((diff.y <= -100 || diff.y >= 100) && Math.abs(diff.y) > Math.abs(diff.x)) {
                    nextStep = diff.y < 0 ? self._currentStep - 1 : self._currentStep + 1;
                    self.customScrollTo(nextStep);
                }
                return false;
            });

            return false;
        });

        return this;
    },

demo (self signed ssl, don't worry!): https://sandbox.idev.ge/roomshotel/html5_v3/

Problem: Scroll jumps straight to bottom when touch is activated.

Expected result: One touch interaction equals 1 section scrolled.

Any thoughts?


Solution

  • I too think the touchmove event's callback is being fired on every touch move. By returning false from that function you only cancel that single touch move event and not all following touch move events.

    You cannot use a touchend event since you want to call self.customScrollTo(nextStep); as soon as the pointer has travelled 100px.

    You want to prevent your touchmove callback from being executed after the pointer has travelled 100px, this can be done in many ways, ie.

    1. Using a flag variable like var trackPointer = true;, check this flag each time touchmove is being triggered and set this flag to false when the pointer has travelled 100px.
    2. When the pointer has travelled 100px, set the startEvent to null and check this variable on touchmove.
    3. Unbind the touchmove event when the pointer has travelled 100px.

    NB: The touchmove event is being bound each time touchstart is triggered on this element, these events do not overwrite each other but get stacked! So you might want to consider binding the event only once (ie. on DOM ready) or unbind the event when it's no longer necessary.

    The latter is probably the easiest and could be done ie. on touchend (use namespaces just to be sure to not unbind the same events bound by other scripts):

    // Unbind all touchmove.myNameSpace events on touchend.myNameSpace.
    self._$body.bind('touchend.myNameSpace').function (event) {
      self._$body.unbind('touchmove.myNameSpace');
    });
    

    and when the pointer has travelled 100px:

    self.customScrollTo(nextStep);
    // Unbind all touchmove.myNameSpace events.
    self._$body.unbind('touchmove.myNameSpace');
    

    Since 'touchend' is not triggered when the pointer is outside the element (I am not sure about touchmove), you might also want to unbind right before binding:

    event.preventDefault();
    // Unbind all touchmove.myNameSpace events and (re)bind touchmove.myNameSpace event.
    self._$body.unbind('touchmove.myNameSpace').bind('touchmove.myNameSpace', function (event) {
      var moveEvent = event.originalEvent.touches[0];
    

    So you could try (I have not tested it):

    /**
     * Initialize touch event listener.
     *
     * @returns {Plugin}
     */
    touch: function () {
        var self = this;
    
        this._$body.bind('touchstart', function (event) {
            var startEvent = event.originalEvent.touches[0];
    
            event.preventDefault();
            self._$body.unbind('touchmove.myNameSpace').bind('touchmove.myNameSpace', function (event) {
                var moveEvent = event.originalEvent.touches[0];
                var diff = { x: startEvent.clientX - moveEvent.clientX, y: startEvent.clientY - moveEvent.clientY };
                var nextStep;
                event.preventDefault(); // <- Not necessary since you completely cancel the event by returning false.
                if ((diff.y <= -100 || diff.y >= 100) && Math.abs(diff.y) > Math.abs(diff.x)) {
                    nextStep = diff.y < 0 ? self._currentStep - 1 : self._currentStep + 1;
                    self.customScrollTo(nextStep);
    
                    // Unbind all touchmove.myNameSpace events.
                    self._$body.unbind('touchmove.myNameSpace');
                }
                return false;
            });
    
            return false;
        });
    
        // Unbind all touchmove.myNameSpace events on touchend.myNameSpace.
        self._$body.bind('touchend.myNameSpace').function (event) {
            self._$body.unbind('touchmove.myNameSpace');
        });
    
        return this;
    },
    

    PS: You might want to use a library like HammerJS (https://github.com/hammerjs/hammer.js) to make gestures work cross browser and also on non-touch devices.