Comment lier les événements' touchstart 'et' click ' mais ne pas répondre aux deux?
je travaille sur un site web mobile qui doit fonctionner sur une variété d'appareils. Celui qui me donne la migraine en ce moment, C'est BlackBerry.
nous devons prendre en charge les clics de clavier ainsi que les événements tactiles.
idéalement, Je n'utiliserais que:
$thing.click(function(){...})
mais le problème que nous rencontrons est que certains de ces appareils blackberry ont un délai très ennuyeux à partir du moment de la touche à elle déclenchant un clic.
le remède est plutôt d'utiliser touchstart:
$thing.bind('touchstart', function(event){...})
mais comment lier les deux événements, mais en tirer un seul? J'ai toujours besoin de l'événement click pour les appareils à clavier, mais bien sûr, je ne veux pas que l'événement click se déclenche si j'utilise un appareil tactile.
une question bonus: y a-t-il de toute façon à faire cela et en plus accommoder les navigateurs qui n'ont même pas un événement touchstart? Dans cette recherche, il ressemble à BlackBerry OS5 ne supporte pas touchstart donc devra également compter sur click events pour ce navigateur.
ADDENDUM:
peut-être une question plus complète est:
avec jQuery, est-il possible / recommandé de gérer à la fois les interactions tactiles et les interactions avec la souris avec les mêmes fixations?
Idéalement, la réponse est oui. Sinon, j'ai quelques options:
1) nous utilisons WURFL pour obtenir les informations de périphérique afin pourrait créer notre propre matrice de dispositifs. En fonction de l'appareil, nous utiliserons touchstart ou click.
2) Detect for touch support dans le navigateur via JS (j'ai besoin de faire plus de recherches à ce sujet, mais il semble que c'est faisable).
cependant, cela laisse encore un problème: qu'en est-il des dispositifs qui supportent les deux. Certains des téléphones que nous supportons (à savoir les Nokias et les Blackberry) ont à la fois des écrans tactiles et clavier. Cela me ramène à la question initiale ... y a-t-il un moyen de permettre les deux à la fois?
30 réponses
mise à jour : consultez le projet jQuery Pointer Events Polyfill qui vous permet de vous lier aux événements "pointer" au lieu de choisir entre la souris et le toucher.
lient aux deux, mais font un drapeau de sorte que la fonction ne démarre qu'une fois pour 100ms ou plus.
var flag = false;
$thing.bind('touchstart click', function(){
if (!flag) {
flag = true;
setTimeout(function(){ flag = false; }, 100);
// do something
}
return false
});
C'est le correctif que je" crée " et il sort le GhostClick et implémente le FastClick. Essayez par vous-même et dites-nous si cela a fonctionné pour vous.
$(document).on('touchstart click', '.myBtn', function(event){
if(event.handled === false) return
event.stopPropagation();
event.preventDefault();
event.handled = true;
// Do your magic here
});
vous pourriez essayer quelque chose comme ceci:
var clickEventType=((document.ontouchstart!==null)?'click':'touchstart');
$("#mylink").bind(clickEventType, myClickHandler);
cela fonctionne généralement aussi bien:
$('#buttonId').on('touchstart click', function(e){
e.stopPropagation(); e.preventDefault();
//your code here
});
le simple fait d'ajouter return false;
à la fin de la fonction on("click touchstart")
peut résoudre ce problème.
$(this).on("click touchstart", function() {
// Do things
return false;
});
de la documentation de jQuery sur .on ()
Retour "à la 151930920" à partir d'un gestionnaire d'événements va automatiquement appeler
event.stopPropagation()
etevent.preventDefault()
. Une valeurfalse
peut également être transmise pour le handler en raccourci pourfunction(){ return false; }
.
j'ai dû faire quelque chose de similaire. Voici une version simplifiée de ce qui a fonctionné pour moi. Si un événement tactile est détecté, supprimez la liaison de clic.
$thing.on('touchstart click', function(event){
if (event.type == "touchstart")
$(this).off('click');
//your code here
});
dans mon cas, l'événement click était lié à un élément <a>
, donc j'ai dû supprimer la liaison click et redémarrer un événement click qui empêchait l'action par défaut pour l'élément <a>
.
$thing.on('touchstart click', function(event){
if (event.type == "touchstart")
$(this).off('click').on('click', function(e){ e.preventDefault(); });
//your code here
});
j'ai suivi la voie suivante.
Simple Comme Bonjour...
$(this).on('touchstart click', function(e){
e.preventDefault();
//do your stuff here
});
généralement, vous ne voulez pas mélanger l'api par défaut touch et non-touch (click). Une fois que vous entrez dans le monde du toucher, il est plus facile de ne traiter que les fonctions liées au toucher. Ci-dessous, un pseudo code qui ferait ce que vous voulez.
si vous vous connectez dans l'événement touchmove et suivez les emplacements, vous pouvez ajouter plus d'éléments dans la fonction doTouchLogic pour détecter les gestes et autres.
var touchStartTime;
var touchStartLocation;
var touchEndTime;
var touchEndLocation;
$thing.bind('touchstart'), function() {
var d = new Date();
touchStartTime = d.getTime();
touchStartLocation = mouse.location(x,y);
});
$thing.bind('touchend'), function() {
var d = new Date();
touchEndTime= d.getTime();
touchEndLocation= mouse.location(x,y);
doTouchLogic();
});
function doTouchLogic() {
var distance = touchEndLocation - touchStartLocation;
var duration = touchEndTime - touchStartTime;
if (duration <= 100ms && distance <= 10px) {
// Person tapped their finger (do click/tap stuff here)
}
if (duration > 100ms && distance <= 10px) {
// Person pressed their finger (not a quick tap)
}
if (duration <= 100ms && distance > 10px) {
// Person flicked their finger
}
if (duration > 100ms && distance > 10px) {
// Person dragged their finger
}
}
je crois que la meilleure pratique consiste maintenant à utiliser:
$('#object').on('touchend mouseup', function () { });
touchend
l'événement touchend est déclenché lorsqu'un point de contact est enlevé de la surface de contact.
l'événement touchend déclenchera et non tous les événements de la souris.
mouseup
L'événement mouseup est envoyé à un élément lorsque le pointeur de la souris sur l'élément, et le bouton de la souris est relâché. Tout élément HTML peut recevoir cet événement.
l'événement mouseup et non déclenchera des événements tactiles.
exemple
$('#click').on('mouseup', function () { alert('Event detected'); });
$('#touch').on('touchend', function () { alert('Event detected'); });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h1 id="click">Click me</h1>
<h1 id="touch">Touch me</h1>
EDIT (2017)
à partir de 2017, les navigateurs commençant par Chrome et faisant des pas vers rendre l'événement de clic .on("click")
plus compatible à la fois pour la souris et le toucher en éliminant le retard généré par les événements de robinet sur les demandes de clic.
Cela conduit à la conclusion que le retour à l'événement click serait la solution la plus simple aller de l'avant.
Je n'ai pas encore fait de test de navigation croisée pour voir si c'est pratique.
vérifier les boutons rapides et cliquez sur Google https://developers.google.com/mobile/articles/fast_buttons
bien... Tout ça est super compliqué.
si vous avez modernizr, c'est une évidence.
ev = Modernizr.touch ? 'touchstart' : 'click';
$('#menu').on(ev, '[href="#open-menu"]', function(){
//winning
});
une autre implémentation pour une meilleure maintenance. Cependant, cette technique fera également événement.stopPropagation (). Le clic n'est saisi sur aucun autre élément qui a cliqué pour 100ms.
var clickObject = {
flag: false,
isAlreadyClicked: function () {
var wasClicked = clickObject.flag;
clickObject.flag = true;
setTimeout(function () { clickObject.flag = false; }, 100);
return wasClicked;
}
};
$("#myButton").bind("click touchstart", function (event) {
if (!clickObject.isAlreadyClicked()) {
...
}
}
juste à des fins de documentation, voici ce que j'ai fait pour le plus rapide / le plus réactif des clics sur le bureau/tapez sur la solution mobile à laquelle je pourrais penser:
j'ai remplacé la fonction on
de jQuery par une fonction modifiée qui, chaque fois que le navigateur prend en charge des événements tactiles, remplace tous mes événements cliquables par touchstart.
$.fn.extend({ _on: (function(){ return $.fn.on; })() });
$.fn.extend({
on: (function(){
var isTouchSupported = 'ontouchstart' in window || window.DocumentTouch && document instanceof DocumentTouch;
return function( types, selector, data, fn, one ) {
if (typeof types == 'string' && isTouchSupported && !(types.match(/touch/gi))) types = types.replace(/click/gi, 'touchstart');
return this._on( types, selector, data, fn);
};
}()),
});
"151930920 d'Utilisation" que serait exactement le même qu'avant, comme:
$('#my-button').on('click', function(){ /* ... */ });
mais il serait utilisez touchstart lorsque disponible, cliquez lorsque Non. Aucun retard d'aucune sorte nécessaire: d
je viens d'avoir l'idée de mémoriser si ontouchstart
était déclenché. Dans ce cas, nous sommes sur un périphérique qui le supporte et nous voulons ignorer l'événement onclick
. Puisque ontouchstart
devrait toujours être déclenché avant "151970920 onclick
, j'utilise ceci:
<script> touchAvailable = false; </script>
<button ontouchstart="touchAvailable=true; myFunction();" onclick="if(!touchAvailable) myFunction();">Button</button>
vous pourriez essayer comme ceci:
var clickEvent = (('ontouchstart' in document.documentElement)?'touchstart':'click');
$("#mylink").on(clickEvent, myClickHandler);
cela a fonctionné pour moi, mobile écoute les deux, donc éviter l'un, qui est l'événement touch. bureau d'écouter de la souris.
$btnUp.bind('touchstart mousedown',function(e){
e.preventDefault();
if (e.type === 'touchstart') {
return;
}
var val = _step( _options.arrowStep );
_evt('Button', [val, true]);
});
dans mon cas cela a fonctionné parfaitement:
jQuery(document).on('mouseup keydown touchend', function (event) {
var eventType = event.type;
if (eventType == 'touchend') {
jQuery(this).off('mouseup');
}
});
le problème principal était quand à la place mouseup j'ai essayé avec le clic, sur les appareils tactiles déclenché le clic et le toucher en même temps, si j'utilise le clic off, certaines fonctionnalités n'a pas fonctionné du tout sur les appareils mobiles. Le problème avec click est qu'il s'agit d'un événement global qui déclenche le reste de l'événement, y compris touchend.
je travaille aussi sur une application Web Android/iPad, et il semble que si seulement utiliser" touchmove "est suffisant pour" déplacer des composants " (Pas besoin de touchstart ). En désactivant touchstart, vous pouvez l'utiliser .cliquez sur(); jQuery. Il fonctionne en fait parce qu'il n'a pas été surchargé par touchstart.
enfin, vous pouvez binb .live("évènements touchstart", function(e) { e.stopPropagation(); }); poser les évènements touchstart événement pour arrêter la multiplication, de la salle de séjour et cliquez sur() pour obtenir déclenchée.
ça a marché pour moi.
il y a beaucoup de choses à considérer quand on essaie de résoudre ce problème. La plupart des solutions soit briser le défilement ou ne pas gérer ghost click événements correctement.
pour une solution complète, voir https://developers.google.com/mobile/articles/fast_buttons
NB: vous ne pouvez pas gérer les événements de clic fantôme sur une base par élément. Un clic retardé est déclenché par l'emplacement de l'écran, donc si vos événements de toucher modifient la page d'une certaine manière, le cliquez sur l'événement sera envoyé à la nouvelle version de la page.
Il peut être efficace d'attribuer aux événements 'touchstart mousedown'
ou 'touchend mouseup'
pour éviter les indésirables-effets de l'utilisation de click
.
profitant du fait qu'un clic va toujours suivre un événement tactile, voici ce que j'ai fait pour me débarrasser du" clic fantôme " sans avoir à utiliser des temps morts ou des drapeaux globaux.
$('#buttonId').on('touchstart click', function(event){
if ($(this).data("already")) {
$(this).data("already", false);
return false;
} else if (event.type == "touchstart") {
$(this).data("already", true);
}
//your code here
});
essentiellement chaque fois qu'un événement ontouchstart
se déclenche sur l'élément, un drapeau un ensemble puis enlevé (et ignoré), lorsque le clic vient.
pourquoi ne pas utiliser L'API jQuery Event?
http://learn.jquery.com/events/event-extensions /
j'ai utilisé cet événement simple avec succès. Il est propre, identifiable et assez souple pour être amélioré.
var isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent);
var eventType = isMobile ? "touchstart" : "click";
jQuery.event.special.touchclick = {
bindType: eventType,
delegateType: eventType
};
si vous utilisez jQuery, ce qui suit a plutôt bien fonctionné pour moi:
var callback; // Initialize this to the function which needs to be called
$(target).on("click touchstart", selector, (function (func){
var timer = 0;
return function(e){
if ($.now() - timer < 500) return false;
timer = $.now();
func(e);
}
})(callback));
D'autres solutions sont également bonnes, mais je reliais plusieurs événements dans une boucle et j'avais besoin de la fonction d'auto-appel pour créer une fermeture appropriée. En outre, Je ne voulais pas désactiver la reliure puisque je voulais qu'elle soit invocable sur next click/touchstart.
pourrait aider quelqu'un dans la même situation!
pour les fonctions simples, il suffit de reconnaître le toucher ou cliquez sur le bouton j'utilise le code suivant:
var element = $("#element");
element.click(function(e)
{
if(e.target.ontouchstart !== undefined)
{
console.log( "touch" );
return;
}
console.log( "no touch" );
});
renvoie" touch "si l'événement touchstart est défini et" no touch " si non. Comme je l'ai dit, il s'agit d'une approche simple pour les événements click/tap juste que.
j'essaie ceci et jusqu'à présent cela fonctionne (mais je ne suis que sur Android/Phonegap donc caveat emptor)
function filterEvent( ob, ev ) {
if (ev.type == "touchstart") {
ob.off('click').on('click', function(e){ e.preventDefault(); });
}
}
$('#keypad').on('touchstart click', '.number, .dot', function(event) {
filterEvent( $('#keypad'), event );
console.log( event.type ); // debugging only
... finish handling touch events...
}
Je n'aime pas le fait que je réengage les manipulateurs sur chaque touche, mais tout ce qui est considéré touche ne se produit pas très souvent (en temps informatique!)
j'ai une tonne de handlers comme celui pour '#keypad' donc avoir une fonction simple qui me permet de traiter le problème sans trop de code est la raison pour laquelle je suis allé de cette façon.
étant pour moi la meilleure réponse à celle donnée par Mottie, j'essaie juste de faire son code plus réutilisable, donc c'est ma contribution:
bindBtn ("#loginbutton",loginAction);
function bindBtn(element,action){
var flag = false;
$(element).bind('touchstart click', function(e) {
e.preventDefault();
if (!flag) {
flag = true;
setTimeout(function() {
flag = false;
}, 100);
// do something
action();
}
return false;
});
essayez d'utiliser Virtual Mouse (vmouse) Bindings
de jQuery Mobile
.
C'est un événement virtuel spécialement pour votre cas:
$thing.on('vclick', function(event){ ... });
http://api.jquerymobile.com/vclick /
liste de soutien du navigateur: http://jquerymobile.com/browser-support/1.4 /
EDIT: Mon ancien réponse (basé sur les réponses dans ce fil) n'était pas la voie à suivre pour moi. Je voulais qu'un sous-menu s'étende sur le clic enter ou touch de la souris et s'effondre sur le départ de la souris ou un autre clic touch. Puisque les évènements de souris sont normalement lancés après les évènements touch, il était assez difficile d'écrire des écouteurs d'évènements qui supportent à la fois l'écran tactile et l'entrée de la souris.
plugin jQuery: Tactile Ou de la Souris
j'ai fini par écrire un plugin jQuery appelé " Touch Or Mouse " (897 bytes miniified) qui peut détecter si un événement a été invoqué par un écran tactile ou une souris (sans test de support tactile!). Cela permet à la fois le soutien d'écran tactile et de souris en même temps et complètement séparer leurs événements.
de cette façon, L'OP peut utiliser touchstart
ou touchend
pour réponse aux clics tactiles et click
pour les clics invoqué seulement par une souris.
Démonstration
le premier doit faire ie. le corps de l'élément de suivre les événements tactiles:
$(document.body).touchOrMouse('init');
La souris établit par défaut notre lien avec les éléments et en appelant $body.touchOrMouse('get', e)
nous pouvons savoir si l'événement a été invoqué par un écran tactile ou par une souris.
$('.link').click(function(e) {
var touchOrMouse = $(document.body).touchOrMouse('get', e);
if (touchOrMouse === 'touch') {
// Handle touch click.
}
else if (touchOrMouse === 'mouse') {
// Handle mouse click.
}
}
voir la plugin au travail à http://jsfiddle.net/lmeurs/uo4069nh .
explication
- ce plugin doit être appelé sur ie. l'élément de corps pour suivre les événements
touchstart
ettouchend
, de cette façon l'événementtouchend
ne doit pas être tiré sur l'élément déclencheur (c.-à-d. un lien ou un bouton). Entre ces deux évènements, Ce plugin considère tout évènement de la souris comme étant invoqué par touch. - les évènements de la souris sont lancés seulement après
touchend
, quand un évènement de la souris est lancé dans leghostEventDelay
(option , 1000ms par défaut) aprèstouchend
, ce plugin considère que l'évènement de la souris est invoqué par touch. - en cliquant sur un élément à l'aide d'un écran tactile, l'élément obtient l'état
:active
. L'événementmouseleave
n'est déclenché qu'après que l'élément a perdu cet état par ie. cliquer sur un autre élément. Comme cela pourrait être des secondes (ou minutes!) après le lancement de l'événementmouseenter
, ce plugin conserve la trace du dernier événementmouseenter
d'un élément: si le dernier événementmouseenter
a été invoqué par touch, l'événementmouseleave
suivant est également considéré comme invoqué par touch.
Voici une façon simple de le faire:
// A very simple fast click implementation
$thing.on('click touchstart', function(e) {
if (!$(document).data('trigger')) $(document).data('trigger', e.type);
if (e.type===$(document).data('trigger')) {
// Do your stuff here
}
});
vous sauvegardez essentiellement le premier type d'événement qui est déclenché à la propriété 'trigger' dans l'objet de données de jQuery qui est attaché au document racine, et n'exécutez que lorsque le type d'événement est égal à la valeur dans 'trigger'. Sur les appareils tactiles, la chaîne d'événements serait probablement 'touchstart' suivie de' click'; cependant, le handler 'click' ne sera pas exécuté parce que 'click' ne correspond pas au type d'événement initial enregistré. "déclencheur" ("évènements touchstart").
l'hypothèse, et je crois que c'est une hypothèse sûre, est que votre smartphone ne passera pas spontanément d'un appareil tactile à un appareil de souris ou bien le robinet ne s'enregistrera jamais parce que le type d'événement "trigger" n'est enregistré qu'une fois par chargement de page et "click"ne correspondrait jamais à "touchstart".
voici un codepen vous pouvez jouer avec (Essayez de taper sur le bouton sur un appareil tactile -- il ne devrait pas y avoir de délai de clic): http://codepen.io/thdoan/pen/xVVrOZ
j'ai aussi implémenté ceci comme un simple plugin jQuery qui supporte aussi le filtrage des descendants de jQuery en passant une chaîne de sélection:
// A very simple fast click plugin
// Syntax: .fastClick([selector,] handler)
$.fn.fastClick = function(arg1, arg2) {
var selector, handler;
switch (typeof arg1) {
case 'function':
selector = null;
handler = arg1;
break;
case 'string':
selector = arg1;
if (typeof arg2==='function') handler = arg2;
else return;
break;
default:
return;
}
this.on('click touchstart', selector, function(e) {
if (!$(document).data('trigger')) $(document).data('trigger', e.type);
if (e.type===$(document).data('trigger')) handler.apply(this, arguments);
});
};
Codepen: http://codepen.io/thdoan/pen/GZrBdo/
La meilleure méthode que j'ai trouvé est d'écrire le toucher de l'événement et l'événement d'appeler la normale, cliquez sur l'événement en programmant. De cette façon, vous avez tous vos événements normaux de clic et ensuite vous avez besoin d'ajouter juste un gestionnaire d'événement pour tous les événements de contact. Pour chaque noeud que vous voulez rendre touchable, ajoutez simplement la classe "touchable" à celle-ci pour invoquer le gestionnaire de touches. Avec Jquery, cela fonctionne ainsi avec une certaine logique pour s'assurer que c'est un vrai événement tactile et non un faux positif.
$("body").on("touchstart", ".touchable", function() { //make touchable items fire like a click event
var d1 = new Date();
var n1 = d1.getTime();
setTimeout(function() {
$(".touchable").on("touchend", function(event) {
var d2 = new Date();
var n2 = d2.getTime();
if (n2 - n1 <= 300) {
$(event.target).trigger("click"); //dont do the action here just call real click handler
}
});
}, 50)}).on("click", "#myelement", function() {
//all the behavior i originally wanted
});