PDA

View Full Version : How do I differentiate between a tap and a swipe on a smartphone app



tonybabb
10-28-2013, 12:18 PM
Good Morning,

Thanks to John's extensive help previously I've been progressing well with my talking phrasebook smartphone app and adding lots of content. This has brought me to a new problem: the user needs to be able to swipe the screen to scroll up or down the list of phrases and although swiping works it also plays whatever sound the user happens to have put their finger on to do the swipe. Is there a way I can differentiate between a tap and a swipe so that the sound only plays on a "tap"?

The original code John gave me and that I am using can be seen here www.lawenforcementspanish.com/demo14 (http://www.lawenforcementspanish.com/demo14) . This doesn't have many phrases, but I can add more if that helps.

Thanks,

Tony

jscheuer1
10-28-2013, 02:51 PM
I'm not sure. Looking at the code however, I see that this:


<script src="jquery.min.js"></script>

is not being used. You should be able to get rid of it.

The difference between a tap and a swipe is how long between touchstart and touchend is. So perhaps instead of using tap, you could use those, only executing on touchend if it follows touchstart within - say 50 milliseconds or whatever you can determine is the optimal length of time.

Before we try converting to that from tap. First see if there's a way to put a parameter on the tap listener that does the same thing, a threshold or a duration parameter. I say this because the tap is probably more cross browser. And because I know that, in at least some javascript/mobile interfaces, such a parameter is available. I'm just not sure if there is one for the version of jQuery Mobile that you're using there. I'll see what I can find out. I believe the documentation for that version might be hard to find though.

jscheuer1
10-28-2013, 03:37 PM
Ah, but now I see and remember that we already converted to touchstart from tap:


if(istouch){
document.getElementById(this.getAttribute('data-trigger')).addEventListener('touchstart', playstop, false);
} else {
$('#' + this.getAttribute('data-trigger')).click(playstop);
}

That's probably why this is happening. Try replacing the above with:


if(istouch){
var dt = document.getElementById(this.getAttribute('data-trigger'));
dt.addEventListener('touchstart', function(e){e.preventDefault(); this.setAttribute('data-start', new Date().getTime())}, false);
dt.addEventListener('touchend', function(e){
var ds;
if(ds = this.getAttribute('data-start') && new Date().getTime() - ds < 100){
playstop.apply(this, [e]);
}
}, false);
} else {
$('#' + this.getAttribute('data-trigger')).click(playstop);
}

I can't test it because I'm not on a mobile. It doesn't throw any obvious errors though. I chose 100 milliseconds (red in the above) as the duration, if the tap lasts longer than that, it's assumed to be something else. That number may need to be increased or decreased.

tonybabb
10-31-2013, 01:57 AM
Ah, but now I see and remember that we already converted to touchstart from tap:


if(istouch){
document.getElementById(this.getAttribute('data-trigger')).addEventListener('touchstart', playstop, false);
} else {
$('#' + this.getAttribute('data-trigger')).click(playstop);
}

That's probably why this is happening. Try replacing the above with:


if(istouch){
var dt = document.getElementById(this.getAttribute('data-trigger'));
dt.addEventListener('touchstart', function(e){e.preventDefault(); this.setAttribute('data-start', new Date().getTime())}, false);
dt.addEventListener('touchend', function(e){
var ds;
if(ds = this.getAttribute('data-start') && new Date().getTime() - ds < 100){
playstop.apply(this, [e]);
}
}, false);
} else {
$('#' + this.getAttribute('data-trigger')).click(playstop);
}

I can't test it because I'm not on a mobile. It doesn't throw any obvious errors though. I chose 100 milliseconds (red in the above) as the duration, if the tap lasts longer than that, it's assumed to be something else. That number may need to be increased or decreased.

Hi John,

Thanks for the help. I tried it with 100ms and it didn't work - i.e. no sound and also no swipe on my droid phone, although it plays a sound with a click on my PC. So, I tried 500ms and also 1000 ms, assuming I wasn't tapping fast enough so increased the threshold. This gave the same result - no click and no swipe on the phone. Below is the code - I can put this online if that helps, just let me know.


<script type="text/javascript">
jQuery(function($){
var highlight = 'yellow', origcolor = 'transparent', istouch = !!('ontouchstart' in window) || !!('ontouchstart' in document.documentElement) || !!window.ontouchstart || (!!window.Touch && !!window.Touch.length) || !!window.onmsgesturechange || (window.DocumentTouch && window.document instanceof window.DocumentTouch);
var $s = $('.spanish').each(function(i, snd){
$(snd).jPlayer({
ready: function() {
$(snd).jPlayer("setMedia", {
mp3: snd.href,
ogg: snd.href.replace(/\.mp3$/, '.ogg')
});
var playstop = function (e) {
e && e.preventDefault();
var $this = $(this);
if(!$this.data('play')){
$this.stop(true, true).css({backgroundColor: highlight});
var $playing = $('a[data-playing="true"]');
if($playing.length){
if(istouch){
playstop.apply($playing.get(0));
} else {
$playing.trigger('click');
}
}
} else {
$this.stop(true, true).animate({backgroundColor: origcolor}, 1000);
}
$(snd).jPlayer($this.data('play')? "stop" : "play");
$this.data('play', !$this.data('play'));
$this.attr('data-playing', $this.data('play'));
};
if(istouch){
var dt = document.getElementById(this.getAttribute('data-trigger'));
dt.addEventListener('touchstart', function(e){e.preventDefault(); this.setAttribute('data-start', new Date().getTime())}, false);
dt.addEventListener('touchend', function(e){
var ds;
if(ds = this.getAttribute('data-start') && new Date().getTime() - ds < 1000){
playstop.apply(this, [e]);
}
}, false);
} else {
$('#' + this.getAttribute('data-trigger')).click(playstop);
}
},
ended: function(){
$('#' + this.getAttribute('data-trigger')).attr('data-playing', 'false').data('play', false).stop(true, true).animate({backgroundColor: origcolor}, 1000);
},
swfPath: "./"
});
});
});
</script>

jscheuer1
10-31-2013, 06:21 AM
Well the first thing to do is to make sure our event is registering. So do a test by changing:


dt.addEventListener('touchend', function(e){
var ds;
if(ds = this.getAttribute('data-start') && new Date().getTime() - ds < 1000){
playstop.apply(this, [e]);
}
}, false);

to:


dt.addEventListener('touchend', function(e){
alert('touchend');
}, false);

tonybabb
10-31-2013, 11:24 AM
It doesn't seem to be registering, when I tried on my PC no alert was displayed. Code can be seen here www.lawenforcementspanish.com/mle14 (http://www.lawenforcementspanish.com/mle14)

Thanks,

Tony

jscheuer1
10-31-2013, 12:34 PM
Is youe PC touch capable? If not, it will not. This has to be tested on the target device.

tonybabb
10-31-2013, 06:47 PM
Hi John,

Doh... yes you're right. I created the app and downloaded to my droid and the alert message appears on the phone. Should I restore the code to before the alert was added?

Tony

jscheuer1
11-01-2013, 06:39 AM
Yes and no. Looking at it I see a possible problem, let's make it:


dt.addEventListener('touchend', function(e){
var ds = this.getAttribute('data-start');
if(ds && new Date().getTime() - ds < 400){
playstop.apply(this, [e]);
}
}, false);

tonybabb
11-01-2013, 12:28 PM
Hi John,

I tried this on a short version of the file and it worked, played sounds and scrolled - although the scrolling was a bit erratic for some reason. Anyway, I then copied the changed code to the full version and messed up somehow. So I went back twice to the original and tried recreating the code with the various changes we made and now I can't get it to work - it won't play sounds and the background color doesn't change, but it does scroll.

Below is the code with the changes we made, but I must have missed something. I'd really appreciate it if you could take a look and let me know what's wrong. When I run this on the droid no sound and no change in background color. Thank you


<script type="text/javascript">
jQuery(function($){
var highlight = 'yellow', origcolor = 'transparent', istouch = !!('ontouchstart' in window) || !!('ontouchstart' in document.documentElement) || !!window.ontouchstart || (!!window.Touch && !!window.Touch.length) || !!window.onmsgesturechange || (window.DocumentTouch && window.document instanceof window.DocumentTouch);
var $s = $('.spanish').each(function(i, snd){
$(snd).jPlayer({
ready: function() {
$(snd).jPlayer("setMedia", {
mp3: snd.href,
ogg: snd.href.replace(/\.mp3$/, '.ogg')
});
var playstop = function (e) {
e && e.preventDefault();
var $this = $(this);
if(!$this.data('play')){
$this.stop(true, true).css({backgroundColor: highlight});
var $playing = $('a[data-playing="true"]');
if($playing.length){
if(istouch){
playstop.apply($playing.get(0));
} else {
$playing.trigger('click');
}
}
} else {
$this.stop(true, true).animate({backgroundColor: origcolor}, 1000);
}
$(snd).jPlayer($this.data('play')? "stop" : "play");
$this.data('play', !$this.data('play'));
$this.attr('data-playing', $this.data('play'));
};
if(istouch){
var dt = document.getElementById(this.getAttribute('data-trigger'));
dt.addEventListener('touchend', function(e){
var ds = this.getAttribute('data-start');
if(ds && new Date().getTime() - ds < 400){
playstop.apply(this, [e]);
}
}, false);
dt.addEventListener('touchend', function(e){
var ds;
if(ds = this.getAttribute('data-start') && new Date().getTime() - ds < 100){
playstop.apply(this, [e]);
}
}, false);
} else {
$('#' + this.getAttribute('data-trigger')).click(playstop);
}
},
ended: function(){
$('#' + this.getAttribute('data-trigger')).attr('data-playing', 'false').data('play', false).stop(true, true).animate({backgroundColor: origcolor}, 1000);
},
swfPath: "./"
});
});
});
</script>

jscheuer1
11-01-2013, 01:53 PM
This from your post:


if(istouch){
var dt = document.getElementById(this.getAttribute('data-trigger'));
dt.addEventListener('touchend', function(e){
var ds = this.getAttribute('data-start');
if(ds && new Date().getTime() - ds < 400){
playstop.apply(this, [e]);
}
}, false);
dt.addEventListener('touchend', function(e){
var ds;
if(ds = this.getAttribute('data-start') && new Date().getTime() - ds < 100){
playstop.apply(this, [e]);
}
}, false);
} else {

should be:


if(istouch){
var dt = document.getElementById(this.getAttribute('data-trigger'));
dt.addEventListener('touchstart', function(e){e.preventDefault(); this.setAttribute('data-start', new Date().getTime())}, false);
dt.addEventListener('touchend', function(e){
var ds = this.getAttribute('data-start');
if(ds && new Date().getTime() - ds < 400){
playstop.apply(this, [e]);
}
}, false);
} else {

Scrolling erratic in what way?

Unrelated, but might fix that - I have an idea to use the x/y coordinates of the touchstart and touchend events to determine if it's a tap or a swipe. If they occur within - say 10px of each other it's a tap, otherwise it's a swipe. That might be more precise than timing the length of time between touchstart and touchend. Both methods might be combined for optimal performance.

tonybabb
11-02-2013, 04:52 AM
Hi John,

Thank you very much, that fixed it, I can now play sounds and scroll the page. Re the erratic scrolling I mentioned previously, that still occurs. It's a little hard to describe but if you recall there's one line of English followed by two lines of Spanish - the formal and informal and this is repeated many times on a page. When I swipe the screen pressing on an English phrase it consistently behaves as I'd expect, whether swiping slow or fast. When I first load a page and swipe on a Spanish phrase it seems to swipe normally both slow and fast but after a few seconds only responds to vigorous swipes and sometimes doesn't scroll at all, unless I carefully swipe on the English phrase.

Thanks again for your ongoing support.

Tony

jscheuer1
11-02-2013, 12:16 PM
We can experiment with removing the preventDefault:


if(istouch){
var dt = document.getElementById(this.getAttribute('data-trigger'));
dt.addEventListener('touchstart', function(e){this.setAttribute('data-start', new Date().getTime())}, false);
dt.addEventListener('touchend', function(e){
var ds = this.getAttribute('data-start');
if(ds && new Date().getTime() - ds < 400){
playstop.apply(this, [e]);
}
}, false);
} else {

BTW, I went ahead and wrote the code I was mentioning before, you can try that as well if you like. Only use one or the other, these are not to be combined:


if(istouch){
var dt = this.getAttribute('data-trigger'), $dt = $('#' + dt);
dt = document.getElementById(dt);
dt.addEventListener('touchstart', function(e){$dt.data({tstart: new Date().getTime(), tx: e.pageX, ty: e.pageY});}, false);
dt.addEventListener('touchend', function(e){
var ds = $dt.data('tstart');
if(!ds){return;}
var vx = Math.abs(e.pageX - $dt.data('tx')), vy = Math.abs(e.pageY - $dt.data('ty'));
if(vx < 10 && vy < 10 && new Date().getTime() - ds < 400){
playstop.apply(this, [e]);
$dt.data({tstart: null, tx: null, ty: null});
}
}, false);
} else {

tonybabb
11-02-2013, 01:06 PM
Hi John,

Thank you so much, both changes worked. I tried the preventDefault change first, that worked but occasionally I found it played a sound when I swiped the screen. Next I tried the new version testing for time and movement and this worked perfectly.

I really appreciate your help with this.

I'm working through some other issues regarding changing text size by the user as there's a fairly wide range of pixel densities and screen sizes that users may be using, so they need to be able to change text size. I'll try and fix them myself before I come to you. I'm gradually learning javascript and the HTML5 Developers Conference last week in San Francisco was well worth 3 days of my time, I wish I had taken it several months ago as I learned a lot during the sessions and a one day training course on jQuery Mobile.

Thanks again,

Tony