I need to detect and react to left/right-swipes, but want to give the user the ability to scroll on the same element, so as long as he moves his finger only left/right with
Detecting left and right while touch is still moving.
This is done with saving last position and using timeout for erasing last position after touchmove stop.
var currentX;
var lastX = 0;
var lastT;
$(document).bind('touchmove', function(e) {
// If still moving clear last setTimeout
clearTimeout(lastT);
currentX = e.originalEvent.touches[0].clientX;
// After stoping or first moving
if(lastX == 0) {
lastX = currentX;
}
if(currentX < lastX) {
// Left
} else if(currentX > lastX){
// Right
}
// Save last position
lastX = currentX;
// Check if moving is done
lastT = setTimeout(function() {
lastX = 0;
}, 100);
});
There is a "bug" in the accepted answer. If you don't use Chrome on Android but the build in browser or a "webview" (For a html5-hybrid-app) for example, then the swipe is not being detected.
I found out that the event doesn't fire, because of the normal scroll behavior. So adding "e.preventDefault();" in touchmove would fix it or the fix from Eric Fuller in the accepted answer.
It's a nice snipped but in a mobile WebApp or Website this could result in a bad scroll stuttering, because the touch-events are observed the whole time.
So I decided to build something new. It's not as comfortable like to have new event listeners, but it's comfortable enough for my needs and it's performat.
function detectswipe(el,func) {
swipe_det = new Object();
swipe_det.sX = 0;
swipe_det.sY = 0;
swipe_det.eX = 0;
swipe_det.eY = 0;
var min_x = 20; //min x swipe for horizontal swipe
var max_x = 40; //max x difference for vertical swipe
var min_y = 40; //min y swipe for vertical swipe
var max_y = 50; //max y difference for horizontal swipe
var direc = "";
ele = document.getElementById(el);
ele.addEventListener('touchstart',function(e){
var t = e.touches[0];
swipe_det.sX = t.screenX;
swipe_det.sY = t.screenY;
},false);
ele.addEventListener('touchmove',function(e){
e.preventDefault();
var t = e.touches[0];
swipe_det.eX = t.screenX;
swipe_det.eY = t.screenY;
},false);
ele.addEventListener('touchend',function(e){
//horizontal detection
if ((((swipe_det.eX - min_x > swipe_det.sX) || (swipe_det.eX + min_x < swipe_det.sX)) && ((swipe_det.eY < swipe_det.sY + max_y) && (swipe_det.sY > swipe_det.eY - max_y)))) {
if(swipe_det.eX > swipe_det.sX) direc = "r";
else direc = "l";
}
//vertical detection
if ((((swipe_det.eY - min_y > swipe_det.sY) || (swipe_det.eY + min_y < swipe_det.sY)) && ((swipe_det.eX < swipe_det.sX + max_x) && (swipe_det.sX > swipe_det.eX - max_x)))) {
if(swipe_det.eY > swipe_det.sY) direc = "d";
else direc = "u";
}
if (direc != "") {
if(typeof func == 'function') func(el,direc);
}
direc = "";
},false);
}
myfunction(el,d) {
alert("you swiped on element with id '"+el+"' to "+d+" direction");
}
To use the function just use it like
detectswipe('an_element_id',myfunction);
detectswipe('an_other_element_id',my_other_function);
If a swipe is detected the function "myfunction" is called with parameter element-id and "l,r,u,d" (left,right,up,down).
Example: http://jsfiddle.net/rvuayqeo/1/
All of these codes need improvement (like most of the codes that you can find on touch manipulation).
When playing with touch event, keep in mind that user have more than one finger, that a touch has an identifier and that touches
list represent all current touches on the surface, even touches that have not moved.
So the process is relatively simple:
ontouchstart: get the first changed touch (not event.originalEvent.touches
property, but event.originalEvent.changedTouches
one). Register its identifier with event.originalEvent.changedTouches[0].identifier
and touch properties to look for (pageX
/pageY
or clientX
/clientY
that are pretty usefull in combination with DOMElement.getBoundingClientRect()
method);
ontouchmove: make sure that the current touch is in the changedTouches list with event.originalEvent.changedTouches.identifiedTouch( identifier )
. If it return nothing, that means that the user has moved another touch (not the one you are looking for). Also register touch properties to look for and do whatever you want with.
ontouchend: again, you must be sure the current touch is in changedTouches list. Do the job with touch properties and finally discard your current touch identifier.
If you want to do it stronger, consider multiple touches (not only one) to observe.
More information about TouchEvent, TouchList and Touch on: https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Touch_events
Inspired by @cocco I created a better (non-minimized) version:
(function(d) {
// based on original source: https://stackoverflow.com/a/17567696/334451
var newEvent = function(e, name) {
// This style is already deprecated but very well supported in real world: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/initCustomEvent
// in future we want to use CustomEvent function: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent
var a = document.createEvent("CustomEvent");
a.initCustomEvent(name, true, true, e.target);
e.target.dispatchEvent(a);
a = null;
return false
};
var debug = false; // emit info to JS console for all touch events?
var active = false; // flag to tell if touchend should complete the gesture
var min_gesture_length = 20; // minimum gesture length in pixels
var tolerance = 0.3; // value 0 means pixel perfect movement up or down/left or right is required, 0.5 or more means any diagonal will do, values between can be tweaked
var sp = { x: 0, y: 0, px: 0, py: 0 }; // start point
var ep = { x: 0, y: 0, px: 0, py: 0 }; // end point
var touch = {
touchstart: function(e) {
active = true;
t = e.touches[0];
sp = { x: t.screenX, y: t.screenY, px: t.pageX, py: t.pageY };
ep = sp; // make sure we have a sensible end poin in case next event is touchend
debug && console.log("start", sp);
},
touchmove: function(e) {
if (e.touches.length > 1) {
active = false;
debug && console.log("aborting gesture because multiple touches detected");
return;
}
t = e.touches[0];
ep = { x: t.screenX, y: t.screenY, px: t.pageX, py: t.pageY };
debug && console.log("move", ep, sp);
},
touchend: function(e) {
if (!active)
return;
debug && console.log("end", ep, sp);
var dx = Math.abs(ep.x - sp.x);
var dy = Math.abs(ep.y - sp.y);
if (Math.max(dx, dy) < min_gesture_length) {
debug && console.log("ignoring short gesture");
return; // too short gesture, ignore
}
if (dy > dx && dx/dy < tolerance && Math.abs(sp.py - ep.py) > min_gesture_length) { // up or down, ignore if page scrolled with touch
newEvent(e, (ep.y - sp.y < 0 ? 'gesture-up' : 'gesture-down'));
//e.cancelable && e.preventDefault();
}
else if (dx > dy && dy/dx < tolerance && Math.abs(sp.px - ep.px) > min_gesture_length) { // left or right, ignore if page scrolled with touch
newEvent(e, (ep.x - sp.x < 0 ? 'gesture-left' : 'gesture-right'));
//e.cancelable && e.preventDefault();
}
else {
debug && console.log("ignoring diagonal gesture or scrolled content");
}
active = false;
},
touchcancel: function(e) {
debug && console.log("cancelling gesture");
active = false;
}
};
for (var a in touch) {
d.addEventListener(a, touch[a], false);
// TODO: MSIE touch support: https://github.com/CamHenlin/TouchPolyfill
}
})(window.document);
Important changes compared to original version by @cocco:
event.touches[0].screenX/screenY
as the major source of information. The pageX/pageY
properties do not correctly represent the movement of touches on screen because if some piece of page scrolls with the touch, it affects the pageX/pageY
values, too.pageX/pageY
before triggering gesture)Things that would need to be done in the future:
createEvent()
method.pageX/pageY
separate from screenX/screenY
?Usage is as follows:
document.body.addEventListener('gesture-right', function (e) { ... });
or jquery style
$("article").on("gesture-down", function (e) { ... });
I wrote my own touch handler events.maybe this helps you
it checks for:
fast click : 'fc'
swipe left : 'swl'
swipe right : 'swr'
swipe up : 'swu'
swipe down : 'swd'
each check initializes it's correspondent event.but you can scroll and do whatever else you do normally. you just have some new events.
you need swl swr, I aslo suggest to use fc (fastclick) for click events... it's much faster than normal click.
window.onload = function() {
(function(d) {
var
ce = function(e, n) {
var a = document.createEvent("CustomEvent");
a.initCustomEvent(n, true, true, e.target);
e.target.dispatchEvent(a);
a = null;
return false
},
nm = true,
sp = {
x: 0,
y: 0
},
ep = {
x: 0,
y: 0
},
touch = {
touchstart: function(e) {
sp = {
x: e.touches[0].pageX,
y: e.touches[0].pageY
}
},
touchmove: function(e) {
nm = false;
ep = {
x: e.touches[0].pageX,
y: e.touches[0].pageY
}
},
touchend: function(e) {
if (nm) {
ce(e, 'fc')
} else {
var x = ep.x - sp.x,
xr = Math.abs(x),
y = ep.y - sp.y,
yr = Math.abs(y);
if (Math.max(xr, yr) > 20) {
ce(e, (xr > yr ? (x < 0 ? 'swl' : 'swr') : (y < 0 ? 'swu' : 'swd')))
}
};
nm = true
},
touchcancel: function(e) {
nm = false
}
};
for (var a in touch) {
d.addEventListener(a, touch[a], false);
}
})(document);
//EXAMPLE OF USE
var h = function(e) {
console.log(e.type, e)
};
document.body.addEventListener('fc', h, false); // 0-50ms vs 500ms with normal click
document.body.addEventListener('swl', h, false);
document.body.addEventListener('swr', h, false);
document.body.addEventListener('swu', h, false);
document.body.addEventListener('swd', h, false);
}
in this case h is my handler for every type of event and i add the handlers to the body.
for what i understand your question you just have to write
YOURELEMENT.addEventListener('swr',YOURSWIPERIGHTFUNCTION,false);
YOURELEMENT.addEventListener('swl',YOURSWIPELEFTFUNCTION,false);
to handle multiple elements and the same function... just add one handler.
so if you have
<ul id="ul"><li>1</li><li>2</li><li>3</li></ul>
you do:
var deleteli=function(e){
var li=e.target;
console.log('deleting '+li.textContent);
}
document.getElementById('ul').addEventListener('swl',deleteli,false);
same for fc & swr
there is a bug in ios: don't use alert() .. it will execute 2 times.