I have draggable elements with full screen width listed vertically.
I am using a plugin called (jquery.ui.touch-punch) to enable jQuery draggable on mobile. But the problem is that the draggable elements prevent the user from scrolling the page.
$('#novieList .element .content').draggable({
axis: 'x',
revert: function() {
return $(this).position().left < 30;
},
containment: [ 0, 0, 75, 0 ],
scope: 'element',
scroll: false,
delay: 300,
drag: function(event, ui) {
return true;
},
start: function(event, ui) {
// Prevent to drag the element after open it
var left = $(this).position().left;
return left == 0;
},
stop: function(event, ui) {
var left = $(this).position().left;
if (left != 0) {
$(this).offset({left: 75});
}
return true;
}
});
I don't believe commenting out event.preventDefault() in jquery.ui.touch-punch.js works any longer. I tried the same solution and found that jQuery UI draggable itself was blocking the default behavior of a vertical scroll -- even when the element is set to drag only along the x-axis.
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.
I found a solution to that problem at Scrolling jQuery UI touch punch. You have to remove a event.preventDefault() in jquery.ui.touch-punch.js on line 38. So far I have only tested on Sony Xperia Z1 Compact, Android 5, Chrome, but it works very well in a project very similar to one named here.
I had the problem regarding that I could scroll on mobile when my div was dragable, as the CSS of the Jquery UI had the following code set to none, so by reverting the changes by putting them on initial everything worked again!
.ui-draggable-handle {-ms-touch-action:initial!important;touch-action:initial!important}
Related
I wrote this code to add swipe function for an image slider. The slider is working correctly.
However when i perform a right or left swipe there is some vertical scrolling which is distracting and annoying.
I'm storing the reference to touchstart in the touch object.
And on touchend event, if vertical distance (lenY) is more than 50, i trigger preventDefault on the touchstart.
This isn't working.
Simplest option is to call preventDefault directly on touchStart. But the image slider occupies a large part of the mobile screen making scrolling down the page tricky.
I need to pass the lenY (vertical distance) to the touch start handler to prevent default action.
function triggerTouch() {
"use strict";
var tZone = document.getElementById('sl-m'),
touch = {},
startX = 0,
startY = 0,
endX = 0,
endY = 0;
if (tZone) {
tZone.addEventListener('touchstart', function (e) {
startX = e.changedTouches[0].screenX;
startY = e.changedTouches[0].screenY;
// store reference to touch event
touch.start = e;
}, false);
tZone.addEventListener('touchend', function (e) {
endX = e.changedTouches[0].screenX;
endY = e.changedTouches[0].screenY;
var lenX = Math.abs(endX - startX);
var lenY = Math.abs(endY - startY);
// check if user intended to scroll down
if (lenY < 50 && lenX > 50) {
touch.start.preventDefault();
e.preventDefault();
swipe(tZone, startX, endX);
}
}, false);
}
}
Since i haven't got an answer i am posting my own answer, hoping someone can provide the correct implementation.
I ended up using the css overflow property to temporarily disable vertical scroll.
This works perfectly though there is a small side effect. Once you swipe through the image slider, the scroll is disabled.
A swipe upwards is required to restore scroll to the page. Its not noticeable but i still want to figure the right way.
var touch = {};
window.onload = function () {
"use strict";
document.body.addEventListener("touchstart", touchHandler);
document.body.addEventListener("touchend", touchHandler);
};
function touchHandler(e) {
"use strict";
var el = e.target;
if (el.parentNode.id === "sl-m") {
if (e.type === "touchstart") {
touch.startX = e.changedTouches[0].screenX;
touch.startY = e.changedTouches[0].screenY;
} else {
touch.endX = e.changedTouches[0].screenX;
touch.endY = e.changedTouches[0].screenY;
touch.lenX = Math.abs(touch.endX - touch.startX);
touch.lenY = Math.abs(touch.endY - touch.startY);
if (touch.lenY < 20) {
// disable scroll
document.body.style.overflowY = "hidden";
// do swipe related stuff
swipe(el.parentNode);
} else {
// enable scroll if swipe was not intended
document.body.style.overflowY = "scroll";
}
}
} else {
// keep scroll enabled if touch is outside the image slider
document.body.style.overflowY = "scroll";
}
}
I want to share the solution that works for me. The above solution did not work on ios. I am sorry for my English. I do not know english.
function stop(e){
e=e || event;
e.preventDefault;
}
window.onscroll=stop(); //-->Yes, we will use it ..
For example, where you will use;
function move(event){
var finish=event.touches[0].clientX;
var verticalFinish=event.touches[0].clientY;
var diff=finish-strt;
var verticalDiff=verticalStrt-verticalFinish;
var f;
if(diff<0 && (Math.abs(diff)>Math.abs(verticalDiff)/3)){
f=verticalDiff+widthOffset;
slayt[x].style.left=diff+"px";
slayt[x].style.transition="none";
slayt[y].style.left=f+"px";
slayt[y].style.transition="none";
window.onscroll=stop(); //-->we used it here :)
}
else if(diff>0 && (Math.abs(diff)>Math.abs(verticalDiff)/3)){
f=diff-widthOffset;
slayt[x].style.left=diff+"px";
slayt[x].style.transition="none";
slayt[z].style.left=f+"px";
slayt[z].style.transition="none";
window.onscroll=stop();//-->we used it here :)
}
}
but there is a small problem. cancels if there is another function related to scrolling. return true; it does not work. I also write twice if I have a function related to the slider inside and outside the touchend.
function end(event){
//"touchend" related codes...
//bla bla
window.onscroll=function(){m=window.pageYOffset;console.log(m);if(m>=850)
{buton.style.display="block";}else{buton.style.display="none";}}
}
If it is useful, I will be happy...
Update :
I typed wrong. I want to fix. Actually, the scroll event cannot be canceled unfortunately. So the event we canceled above, scroll is not a vertical scroll event. All events.
window.onscroll=stop(); // ==>improper use
stop(); // ==> actually - Correct usage
It just needs to be written so stop().
html,
body {
overflow: hidden;
}
Did you try this?
I have a grid panel in ExtJS with scroll bars. I am trying to detect when the user has scrolled all the way down(so that they can not move bar anymore). So far I have this, which detects when scroll occurs but provides no information(?) about where the scroll bar is.
//bufferedGrid is a grid panel
this.randomGrid.getView().on('scroll', this.onRandomGridScroll, this);
.
.
.
onRandomGridScroll : function(e, t)
{
console.log(e);
console.log(t);
}
Any pointers would be appreciated.
You can access the current scroll bar position(actually, the top of the scroll bar) and the maximum scroll position as follows(works in Firefox but not Chrome):
onBufferedGridScroll : function(e, t)
{
var max = this.bufferedGrid.getView().el.dom.scrollTopMax;
var current = this.bufferedGrid.getView().el.dom.scrollTop;
if( current == max )
alert('You have reached the bottom of the scroll !');
}
Add event on init
After grid is rendered add a mouseup event and a wheel down event.
'container #gridId':{
afterrender: this.addScrollEventListener
}
addScrollEventListener: function(comp){
comp.getTargetEl().on('mouseup', function(e, t) {
var height = comp.getTargetEl().getHeight();
if (height + t.scrollTop >= t.scrollHeight) {
}
});
comp.getTargetEl().on('wheeldown', function(e, t) {
var height = comp.getTargetEl().getHeight();
if (height + t.scrollTop >= t.scrollHeight) {
}
});
}
I am making a slider that have 3 images and they are draggable with limits , I am using UI touch punch to make the mobile drag work.
This is my js code so far :
$(".draggable").draggable({
axis: "x",
drag: function(event, ui) {
//$(window).scrollTo(0, scrollPage);
//window.scrollBy(0,scrollPage);
if (ui.position.left > 0) {
newLeft = 0;
} else if (ui.position.left < max_drag) {
newLeft = max_drag;
} else {
newLeft = ui.position.left;
}
ui.position.left = newLeft;
}
});
and this is what i want to achieve :
now everything works except on mobile when i scroll vertically over the draggable element it doesn't scroll even tho the draggable is only on x axis.
I have searched and fuond this :
jQuery UI draggable prevents scrolling on mobile
And
jQuery UI Draggable and Page Scrolling (on mobile)
but I couldnt get a clear solution or answer.
I've got a home-made slider made from jQuery UI's draggable() function:
$("#petrolGauge .fuelBar .slider").draggable({
containment: "parent",
axis: "x",
drag:function(){
updValues();
},
start:function(){
$(this).css("background-color","#666");
},
stop:function(){
//checkForm();
$(this).css("background-color","#AAA");
}
});
This is for the following markup:
<div id="petrolGauge">
<input id="endPet" name="endPet" type="hidden" value="0">
How much fuel was left in the tank when you were finished? (Use the slider) <b>(~<span class="petLeft">0</span>%)</b>
<span class="mandatory">*</span><br />
<div class="fuelBar">
<div title="Drag" class="slider"></div>
</div>
This works a treat, when I click on the slider. But I'd like it so that when I click the fuel bar (the slider's parent) the slider not only starts dragging but also jumps to the cursor. I've achieved it by doing this:
$("#petrolGauge .fuelBar").on("mousedown",function(e){
slider = $("#petrolGauge .fuelBar .slider");
left = e.pageX-($(this).offset().left)-(slider.width()/2);
updValues();
slider.css("left",left).trigger(e);
});
Two problems with this:
Firstly, when clicking on the parent I get a couple of second's delay before the slider starts to drag? I've tried and tested this in Chrome and IE and both do it. Secondly if the cursor is less than half of the slider's width away from the edge of the parent, the slider will move to the outside of the parent. Wouldn't be hard to fix this with a couple of checking, but was wondering if there was another way? I'm suprised that draggable() doesn't have any parameters for this to be honest. I didn't want to use slider() if I could help it but if it's the only way, then it's the only way.
Here's a fiddle to work with.
The reason you get the delay is because you use .trigger() inside the .on() event which creates a big loop. As a result the loop slows down the moving process.
$("#petrolGauge .fuelBar").click(function (e) { // use click instead of mousedown
slider = $("#petrolGauge .fuelBar .slider");
left = e.pageX - ($(this).offset().left) - (slider.width() / 2);
if(left > 570) { left = 570; } else if(left < 0) { left = 0; }
// it looks like a draggable bug due to the manual position change, so use a small check
slider.css("left", left); // change the position first
updValues(); // then calculate and update the div
// no need to trigger the event a second time because it will loop until jQuery exceeds it's trigger limit.
});
Here's an updated FIDDLE
Updated answer
To make .slider move accordingly to the mouse movement when not directly dragged, bind a mousemove event to the mousedown and unbind it when mouseup. Then in .mousemove() you change the position of .slider.
var move = function (e) {
left = e.pageX - ($('#petrolGauge .fuelBar').offset().left) - (slider.width() / 2);
if (left > 570) {
left = 570;
} else if (left < 0) {
left = 0;
}
slider.css("left", left);
updValues();
};
var slider = $("#petrolGauge .fuelBar .slider");
$("#petrolGauge .fuelBar").mousedown(function (e) {
e.preventDefault();
left = e.pageX - ($(this).offset().left) - (slider.width() / 2);
if (left > 570) {
left = 570;
} else if (left < 0) {
left = 0;
}
slider.css("left", left)
$(this).bind('mousemove', move);
updValues();
}).mouseup(function () {
$(this).unbind('mousemove');
});
I have a div with a javascript touchmove event listener that scrolls the image inside the div horizontally on iOS6 Mobile Safari. I'd like allow vertical scrolling of the page to bubble up to the browser but when this occurs, jQuery.animate no longer works.
I've posted a simplified version of the code that demonstrates the problem at
https://gist.github.com/4047733
The steps I take to recreate the problem are:
Swipe picture left/right and notice how it animates back to the left edge
Touch the picture and scroll the page up/down
Repeat left/right swipe and notice the picture does NOT animate back to the left edge. It appears jQuery animate fails after touchmove occurs without e.preventDefault
Here is the javascript inside jQuery document ready from the gist link above
var el = document.getElementById("swipebox"),
$slider = $('#swipebox').find('img'),
startX, startY, dx, dy,
startLeft,
animateH = false,
animateV = false;
var onTouchStart = function(e) {
startLeft = parseInt($slider.css('left'), 10) || 0;
startX = e.touches[0].pageX;
startY = e.touches[0].pageY;
};
var onTouchMove = function(e) {
dx = e.touches[0].pageX - startX;
dy = e.touches[0].pageY - startY;
if (
animateH ||
(!animateV && Math.abs(dx) > 5)
) {
// prevent default, we are scrolling horizontally,
animateH = true;
$slider.stop().css({'left': startLeft+dx*2});
e.preventDefault();
} else if (Math.abs(dy) > 5) {
// do NOT prevent default, we are scrolling the page vertically
animateV = true;
} else {
// direction of scroll is undetermined at this time
// we've moved less than 5px in any direction
e.preventDefault();
}
return false;
};
var onTouchEnd = function(e) {
$slider.stop().animate({'left': 0}); // animate image back to left
e.preventDefault();
animateH = false;
animateV = false;
};
var onTouchCancel = function(e) {
console.log('onTouchCancel');
};
el.addEventListener('touchstart', onTouchStart, false);
el.addEventListener('touchmove', onTouchMove, false);
el.addEventListener('touchend', onTouchEnd, false);
el.addEventListener("touchcancel", onTouchCancel, false);
Any insight would be greatly appreciated.
This is bug in iOS6. jQuery animate timers fail when scrolling window in iOS6.
Currently there are few workarounds on this:
Create your own timer functions like someone did: https://gist.github.com/3755461
Use CSS3 transition instead of jQuery.animate. This is preffered way - css3 transitions doesn't have such problem. You can use this jquery plugin http://ricostacruz.com/jquery.transit/ to easily manipulate CSS transitions in JavaScript.