Reputation: 938
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));
}
Upvotes: 2
Views: 2630
Reputation: 17651
I tried the OPs solution, but I think jQuery UI draggable
may have changed since you last made this post. In my tests, even after completely removing jquery-ui-touch-punch.js
I found that the drag event was still being consumed by jQuery UI draggable
. I didn't want to dig into the jQuery UI code and fork the library, so I devised a fairly simple alternative.
In my scenario I had an inbox list whose items that you could drag to the left or right to expose action buttons. The entire inbox item must be draggable -- so the use of a drag handle was not an option.
jQuery's draggable
prevents vertical scrolling on touch screens if the touch was initiated inside a draggable
element. So if the screen was filled with draggable inbox items, then the user would become trapped -- unable to scroll up or down.
The solution that worked for me was to measure any change in the cursor's vertical position and use window.scrollBy
to manually scroll the window by the same amount:
var firstY = null;
var lastY = null;
var currentY = null;
var vertScroll = false;
var initAdjustment = 0;
// record the initial position of the cursor on start of the touch
jqDraggableItem.on("touchstart", function(event) {
lastY = currentY = firstY = event.originalEvent.touches[0].pageY;
});
// fires whenever the cursor moves
jqDraggableItem.on("touchmove", function(event) {
currentY = event.originalEvent.touches[0].pageY;
var adjustment = lastY-currentY;
// Mimic native vertical scrolling where scrolling only starts after the
// cursor has moved up or down from its original position by ~30 pixels.
if (vertScroll == false && Math.abs(currentY-firstY) > 30) {
vertScroll = true;
initAdjustment = currentY-firstY;
}
// only apply the adjustment if the user has met the threshold for vertical scrolling
if (vertScroll == true) {
window.scrollBy(0,adjustment + initAdjustment);
lastY = currentY + adjustment;
}
});
// when the user lifts their finger, they will again need to meet the
// threshold before vertical scrolling starts.
jqDraggableItem.on("touchend", function(event) {
vertScroll = false;
});
This will closely mimic native scrolling on a touch device.
Upvotes: 1
Reputation: 938
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');
};
Upvotes: 0