Pazcal de Jonge Pazcal de Jonge - 1 month ago 7
HTML Question

Horizontal dragging is blocking the default vertical scroll of the page

I have created a codepen of what I'm trying to achieve.

I'm creating a timeline overview with a couple of horizontal draggable items. Next to

jquery
and
jquery-ui
, I am also using
jquery-ui-touch-punch
for make all the gestures possible on mobile devices. On desktop everything works fine but not on mobile.

Unfortunately is the
$(".timeLineItemContainer").draggable({ ... });
extremely aggressive that it also swallows my vertical scrolling behavior which results in a page where I can't scroll down on mobile when I touch 1 of the items.

Somehow I need to enable or disable the drag capability of an item, depending on the direction of my scroll action. When I disable the
event.preventDefault();
in the
jquery-ui-touch-punch.js
it scrolls down, only then the
click
and
horizontal move
doesn't work properly (a horizontal swipe on mobile refers to a history.goback, which I want to prevent as I need it for my gesture).

To sum things up: All I want is to prevent the default behaviors, except for the vertical scroll.

Any thoughts of how to achieve this? Or are there any other ideas/approaches to achieve this?

Here's a link for a mobile version

HTML (just a simple list of multiple items)

<div class="timeLineItemContainer">
<div class="subItemMiddleContainer">
<div class="timeLineHeader">
<div>
<span>A TITLE</span>
</div>
<div>
<div>Some other text text</div>
</div>
</div>
</div>
<div class="subItemBottomContainer">
<ul>
<li class="static">
<div class="timeLineRight timeLineInfo">
<span class="info">Thu 12 March</span>
</div>
</li>
<li class="static">
<div class="timeLineRight timeLineInfo">
<span class="label">some additional text</span>
<span class="info time strikethrough">bla</span>
<div class="info time attention">
<span>yup</span>
</div>
</div>
</li>
<li class="toggable closed">
<div class="timeLineRight timeLineInfo">
<span class="label">Text</span>
<span class="info">content</span>
</div>
</li>
<li class="toggable closed">
<div class="timeLineRight timeLineInfo">
<span class="label">title</span>
<span class="info">value</span>
</div>
</li>
<li class="toggable closed">
<div class="timeLineRight timeLineInfo">
<span class="label">Another title</span>
<span class="info">another value</span>
</div>
</li>
<li class="static">
<div class="timeLineRight timeLineInfo">
<span class="label">always visible</span>
<span class="info time strikethrough">just because</span>
<div class="info attention">
<span>attention!</span>
</div>
</div>
</li>
</ul>
</div>
</div>
<div class="timeLineItemContainer">
<div class="subItemMiddleContainer">
<div class="timeLineLeft">
<div class="timeLinePipe"></div>
</div>
<div class="timeLineHeader">
<div>
<span>Some other text</span>
</div>
<div>
<div>Ola!</div>
</div>
</div>
</div>
<div class="subItemBottomContainer">
<ul>

<li class="static">
<div class="timeLineRight timeLineInfo">
<span class="info">A date</span>
</div>
</li>
<li class="toggable closed">
<div class="timeLineRight timeLineInfo">
<span class="label">label</span>
<span class="info">value</span>
</div>
</li>
<li class="toggable closed">
<div class="timeLineRight timeLineInfo">
<span class="label">anotehr label</span>
<span class="info time">different value</span>
</div>
</li>
</ul>
</div>
</div>
<div class="timeLineItemContainer">
<div class="subItemMiddleContainer">
<div class="timeLineHeader">
<div>
<span>A TITLE</span>
</div>
<div>
<div>Some other text text</div>
</div>
</div>
</div>
<div class="subItemBottomContainer">
<ul>
<li class="static">
<div class="timeLineRight timeLineInfo">
<span class="info">Thu 12 March</span>
</div>
</li>
<li class="static">
<div class="timeLineRight timeLineInfo">
<span class="label">some additional text</span>
<span class="info time strikethrough">bla</span>
<div class="info time attention">
<span>yup</span>
</div>
</div>
</li>
<li class="toggable closed">
<div class="timeLineRight timeLineInfo">
<span class="label">Text</span>
<span class="info">content</span>
</div>
</li>
<li class="toggable closed">
<div class="timeLineRight timeLineInfo">
<span class="label">title</span>
<span class="info">value</span>
</div>
</li>
<li class="toggable closed">
<div class="timeLineRight timeLineInfo">
<span class="label">Another title</span>
<span class="info">another value</span>
</div>
</li>
<li class="static">
<div class="timeLineRight timeLineInfo">
<span class="label">always visible</span>
<span class="info time strikethrough">just because</span>
<div class="info attention">
<span>attention!</span>
</div>
</div>
</li>
</ul>
</div>
</div>
<div class="timeLineItemContainer">
<div class="subItemMiddleContainer">
<div class="timeLineLeft">
<div class="timeLinePipe"></div>
</div>
<div class="timeLineHeader">
<div>
<span>Some other text</span>
</div>
<div>
<div>Ola!</div>
</div>
</div>
</div>
<div class="subItemBottomContainer">
<ul>

<li class="static">
<div class="timeLineRight timeLineInfo">
<span class="info">A date</span>
</div>
</li>
<li class="toggable closed">
<div class="timeLineRight timeLineInfo">
<span class="label">label</span>
<span class="info">value</span>
</div>
</li>
<li class="toggable closed">
<div class="timeLineRight timeLineInfo">
<span class="label">anotehr label</span>
<span class="info time">different value</span>
</div>
</li>
</ul>
</div>
</div>


Javascript

var sw = screen.width;
var thresholdPercentageSwipeTimeLineItem = 15;
var thresholdPercentageSwipeDetailScreen = 10;

$(".timeLineItemContainer").draggable({
scroll: false,
axis: "x",
drag: function (event, ui) {
if (ui.position.left < 0) {
ui.position.left = 0;
}
else {
if (calculateOffsetPercentage(ui.position.left) > thresholdPercentageSwipeTimeLineItem) {
return false;
};
}
},
stop: function (event, ui) {
$(this).animate({
left: 0,
}, {
duration: 200,
queue: false
});
}
}).click(function () {
var nextObjs = $(this).toggleClass("visible").find(".toggable");

$.each(nextObjs, function () {
$(this).stop().animate({
height: "toggle",
queue: false
}).toggleClass("closed");
});

});

function calculateOffsetPercentage(screenValue) {
return (100 / (sw / screenValue));
}

Answer

Nailed it!

On first thoughts it looks like the $(".timeLineItemContainer").draggable({ ... }); is consuming all of the events, but it is actually the jquery-ui-touch-punch.js who is preventing everything.

jquery-ui-touch-punch.js works in a way that all the touch events results are prevented to execute the default behavior. Therefore I tweaked the jquery-ui-touch-punch.js a bit to get the right behavior.

  1. I removed the event.preventDefault() from the simulateMouseEvent() function.

  2. I store the start of the touch in the mouseProto._touchStart event

    tsx = event.originalEvent.touches[0].clientX; tsy = event.originalEvent.touches[0].clientY;

  3. I added a condition inside the mouseProto._touchMove event to check if I'm scrolling in a horizontal or vertical direction. In case of a horizontal direction I'm executing the event.preventDefault(). To get to that, you can use:

    // Higher then 1 means a (more) horizontal direction // Lower then 1 means a (more) vertical direction // 1 is an exact 45 degrees swipe, which will be handled as a vertical swipe if ((Math.abs(CurrentX - StartX)) / Math.abs(CurrentY - StartY) > 1) { event.preventDefault(); }

The endresult of the mouseProto._touchStart and the mouseProto._touchMove function look like this:

var tsx = 0, 
    tsy = 0, 
    currentx = 0, 
    currenty = 0;

mouseProto._touchStart = function (event) {
    var self = this;

    tsx = event.originalEvent.touches[0].clientX;
    tsy = event.originalEvent.touches[0].clientY;

    // Ignore the event if another widget is already being handled
    if (touchHandled || !self._mouseCapture(event.originalEvent.changedTouches[0])) {
        return;
    }

    // Set the flag to prevent other widgets from inheriting the touch event
    touchHandled = true;

    // Track movement to determine if interaction was a click
    self._touchMoved = false;

    //// Simulate the mouseover event
    simulateMouseEvent(event, 'mouseover');

    //// Simulate the mousemove event
    simulateMouseEvent(event, 'mousemove');

    // Simulate the mousedown event
    simulateMouseEvent(event, 'mousedown');

};

mouseProto._touchMove = function (event) {
    // Ignore event if not handled
    if (!touchHandled) {
        return;
    }

    // Higher then 1 means a (more) horizontal direction
    // Lower then 1 means a (more) vertical direction
    // 1 is an exact 45 degrees swipe, which will be handled as a vertical swipe
    if ((Math.abs(event.originalEvent.changedTouches[0].clientX - tsx)) / Math.abs(event.originalEvent.changedTouches[0].clientY - tsy) > 1) {
        event.preventDefault();
    }

    // Interaction was not a click
    this._touchMoved = true;

    // Simulate the mousemove event
    simulateMouseEvent(event, 'mousemove');
};
Comments