Laef
Laef

Reputation: 1145

Prevent vertical mobile scrolling on predominantly horizontal touch gesture in JavaScript

I have some JavaScript to handle touch events on a web page and watch for gestures that are predominantly horizontal. When the user begins to swipe horizontally, I want the effects of their gesture to begin, but when they swipe vertically, the page should scroll naturally as it would otherwise.

While horizontal gestures do get the desired effect right now, they also still seem to cause vertical scroll for the amount the touch moves up or down, even while the majority of the movement is horizontal. How would I fix this to prevent vertical scrolling when swiping predominantly horizontally? I've used both preventDefault() and stopPropagation() to no effect. I imagine there's something really obvious I'm missing, but I just can't seem to find it.

I've tried my best to search for similar questions, but my search queries are yet to return anything that works.

Note: I don't want to stop vertical scrolling altogether, and even if there's an X-value change in the gesture, the page should still scroll if the Y-change is greater. I only want the vertical scroll to lock if the X-change is greater than the Y-change (which is what my code should do right now).

Update: I hadn't thought to retrieve console logging from a mobile device. On @tushar-shukla's suggestion, I checked it, discovering the following error:

[Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive. See <URL>

Here is a watered-down version of my code (and a JSFiddle, if that's more your cup of tea):

var startX = undefined;
var startY = undefined;

function touchmove(ev) {
    var e = ev.originalEvent;

    // Set the initial start position if not already. Could also be done on touchstart
    if (startX===undefined) { startX=e.touches[0].pageX; }
    if (startY===undefined) { startY=e.touches[0].pageY; }

    // Calculate the change or displacement from the start.
    changeX = e.touches[0].pageX-startX;
    changeY = e.touches[0].pageY-startY;

    // If the change horizontally is greater than the change vertically...
    if (Math.abs(changeX)>Math.abs(changeY)) {

        // Do something with the gesture

        // Prevent vertical scrolling; Apparently this isn't doing it:
        ev.preventDefault();
        ev.stopPropagation();
        e.preventDefault();
        e.stopPropagation();

    }
}

$(document).on("touchmove",touchmove);

Upvotes: 0

Views: 2292

Answers (2)

Laef
Laef

Reputation: 1145

After hooking up my mobile device to Chrome, I retrieved the console logging, containing the following repeated error:

[Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive. See <URL>

This was the clue I needed to solve the problem. Modern mobile browsers have passive and active event listeners, where active ones can prevent the default action for the event and passive ones can't. This is a performance optimisation for things like touch gestures, which need to run as smoothly as possible, even on bulky web pages.

JavaScript uses passive event listeners automatically for events such as touchmove. While JavaScript offers a parameter when attaching the event listener to change this behaviour, jQuery, unfortunately, does not.

This means, of course, that the problem can be easily remedied by replacing:

$(document).on("touchmove",touchmove);

In my code with:

document.addEventListener('touchmove', pageswiping.touchmove, {passive: false});

In this case, {passive: false} makes it clear that the event listener is an active one, not a passive one.

Upvotes: 1

Tushar Shukla
Tushar Shukla

Reputation: 6605

If I understood it correctly, you need to STOP VERTICAL SCROLL (which is unintended) when user is scrolling (or trying to) horizontally.

I haven't tested it yet but may be this can work.

var startX;
$(document).on('touchstart', function(e) {
  startX = e.originalEvent.touches[0].clientX;
});

$(document).on('touchmove', function(e) {
  var endX = e.originalEvent.changedTouches[0].clientX;
  if (startX !== endX) {
    console.log('attempting horizontal scroll');
    e.preventDefault();
  }
});

Upvotes: 0

Related Questions