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?
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));
}
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.
I removed the event.preventDefault()
from the simulateMouseEvent()
function.
I store the start of the touch in the mouseProto._touchStart
event
tsx = event.originalEvent.touches[0].clientX;
tsy = event.originalEvent.touches[0].clientY;
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');
};