web-dev-qa-db-fra.com

gérer les événements de souris et tactiles sur les écrans tactiles

J'écris une application Web qui devrait prendre en charge les interactions souris et tactile. Pour tester, j'utilise un appareil à écran tactile avec Windows 7. J'ai essayé de détecter les événements tactiles dans les derniers canaris Firefox et Chrome et j'ai obtenu les résultats suivants: :

Au toucher, Firefox déclenche le contact et l'événement correspondant de la souris. Chrome lance touchstart/mousedown, touchend/mouseup paires, mais mousemove a été déclenché de manière très étrange: une/deux fois alors que touchmove.

Tous les événements de souris gérés comme toujours.

Existe-t-il un moyen de gérer la souris et le toucher simultanément sur les écrans tactiles modernes? Si Firefox déclenche une paire d'événements tactiles et de souris, que se passe-t-il sur touchmove avec mousemove dans Chrome? Devrais-je traduire tous les événements de la souris au toucher ou vice versa? J'espère trouver le bon moyen de créer une interface réactive.

11
johnny

Vous devriez plutôt vérifier la disponibilité de l'interface tactile et lier les événements en fonction de cela.

Vous pouvez faire quelque chose comme ça:

(function () {
    if ('ontouchstart' in window) {
        window.Evt = {
            Push : 'touchstart',
            MOVE : 'touchmove',
            RELEASE : 'touchend'
        };
    } else {
        window.Evt = {
            Push : 'mousedown',
            MOVE : 'mousemove',
            RELEASE : 'mouseup'
        };
    }
}());

// and then...

document.getElementById('mydiv').addEventListener(Evt.Push, myStartDragHandler, false);

Si vous souhaitez gérer les deux en même temps et que le navigateur ne convertit pas les événements tactiles en événements de souris, vous pouvez intercepter des événements tactiles et les arrêter - les événements de souris correspondants ne doivent pas être déclenchés par le navigateur (vous ne créerez pas de double événement) et vous pouvez le déclencher vous-même en tant qu'événement de souris ou simplement le gérer.

var mydiv = document.getElementsById('mydiv');
mydiv.addEventListener('mousemove', myMoveHandler, false);
mydiv.addEventListener('touchmove', function (e) {
    // stop touch event
    e.stopPropagation();
    e.preventDefault();

    // translate to mouse event
    var clkEvt = document.createEvent('MouseEvent');
    clkEvt.initMouseEvent('mousemove', true, true, window, e.detail, 
                 e.touches[0].screenX, e.touches[0].screenY, 
                 e.touches[0].clientX, e.touches[0].clientY, 
                 false, false, false, false, 
                 0, null);
    mydiv.dispatchEvent(clkEvt);

    // or just handle touch event
    myMoveHandler(e);
}, false);
6
lupatus

Vous ne pouvez pas vraiment prédire à l'avance quels événements écouter (par exemple, tout ce que vous savez, un écran tactile USB pourrait être branché après le chargement de votre page).

Au lieu de cela, vous devriez toujours écouter à la fois les événements tactiles et les événements de souris, mais appelez preventDefault () sur les événements tactiles que vous gérez pour éviter que des événements de souris (désormais redondants) ne soient déclenchés pour eux. Voir http://www.html5rocks.com/en/mobile/touchandmouse/ pour plus de détails.

14
Rick Byers

MouseEvents et TouchEvents n'offrent pas techniquement les mêmes fonctionnalités, mais peuvent être utilisés de manière interchangeable dans la plupart des cas. Cette solution ne favorise pas l’un l’autre, l’utilisateur pouvant disposer d’une souris et d’un écran tactile. Au lieu de cela, il permet à l'utilisateur d'utiliser le périphérique d'entrée de son choix, à condition d'attendre au moins cinq secondes avant de modifier les entrées. Cette solution ignore l'émulation du pointeur de la souris sur les périphériques à écran tactile lorsque l'écran est tapé. 

var lastEvent = 3  ;
var MOUSE_EVENT = 1;
var TOUCH_EVENT = 2 ; 
 

element.addEventListener('touchstart', function(event)
		{
			 
			if (lastEvent === MOUSE_EVENT  )
			{
				var time =  Date.now() - eventTime  ;  
				if ( time > 5000 ) 
				{
					eventTime = Date.now() ;
					lastEvent = TOUCH_EVENT ;
					interactionStart(event) ;
				} 
			}
			else 
			{
				lastEvent = TOUCH_EVENT ; ;
				eventTime = Date.now() ;
				interactionStart(event)  ; 
			}
			 
		}) ;	

		element.addEventListener('mousedown',	function(event)
		{
			
			if (lastEvent === TOUCH_EVENT  )
			{
				var time =  Date.now() - eventTime  ;	
				if ( time > 5000 ) 
				{
					eventTime = Date.now() ;
					lastEvent = MOUSE_EVENT ;
					interactionStart(event) ;
				} 
			}
			else 
			{
				lastEvent=  MOUSE_EVENT ; 
				eventTime = Date.now() ;
				interactionStart(event)  ; 
			}
		}) ;  

function interactionStart(event) // handle interaction (touch or click ) here.
{...}

Ce n'est en aucun cas une solution gagnante à tous. Je l'ai déjà utilisé plusieurs fois et je n'ai pas rencontré de problèmes. Cependant, pour être honnête, je l'utilise généralement pour lancer une animation lorsque le canevas est tapé ou pour fournir une logique de rotation. une div en un bouton. Je vous laisse le soin d’utiliser ce code, de trouver des améliorations et d’aider à améliorer ce code (si vous ne trouvez pas de meilleure solution). 

1
FutureSci

J'ai trouvé ce fil parce que j'ai un problème similaire et plus complexe:

supposons que nous créons une zone de défilement compatible JS avec les flèches NEXT/PREVIOUS, que nous voulons non seulement répondre aux événements tactiles et de souris, mais également les déclencher de manière répétée pendant que l'utilisateur continue d'appuyer sur l'écran ou de maintenir sa souris!

La répétition d’événements ferait que mon bouton suivant avance de 2 positions au lieu d’une!

Avec l'aide des fermetures, tout semble possible:

(1) Créez d'abord une fonction d'invocation automatique pour l'isolation de variable:

 (function(myScroll, $, window, document, undefined){
 ...
 }(window.myScroll = window.myScroll || {}, jQuery, window, document));

(2) Ensuite, ajoutez vos variables privées qui contiendront l'état interne de setTimeout():

/*
 * Primary events for handlers that respond to more than one event and devices  
 * that produce more than one, like touch devices.
 * The first event in browser's queue hinders all subsequent for the specific 
 * key intended to be used by a handler.
 * Every key points to an object '{primary: <event type>}'.
 */
var eventLock = {};
// Process ids based on keys.
var pids = {};
// Some defaults
var defaults = {
   pressDelay: 100 // ms between successive calls for continuous press by mouse or touch
}

(3) Les fonctions de verrouillage d'événements:

function getEventLock(evt, key){
   if(typeof(eventLock[key]) == 'undefined'){
      eventLock[key] = {};
      eventLock[key].primary = evt.type;
      return true;
   }
   if(evt.type == eventLock[key].primary)
      return true;
   else
      return false;
}
function primaryEventLock(evt, key){
   eventLock[key].primary = evt.type;
}

(4) Attachez vos gestionnaires d'événements:

function init(){
   $('sth').off('mousedown touchstart', previousStart).on('mousedown touchstart', previousStart);
   $('sth').off('mouseup touchend', previousEnd).on('mouseup touchend', previousEnd);
   // similar for 'next*' handlers
}

Le déclenchement des événements mousedown et touchstart générera un double appel des gestionnaires sur les périphériques prenant en charge les deux (probablement des déclenchements tactiles en premier). Il en va de même pour mouseup et touchend.

Nous savons que les périphériques d'entrée (environnements graphiques entiers en réalité) produisent des événements de manière séquentielle. Par conséquent, nous ne tenons pas compte du premier tirage tant que la clé special est définie sur eventLock.next.primary privé et eventLock.previous.primary pour les premiers événements capturés à l'aide des gestionnaires next*() et previous*() respectivement. .

Cette clé est le type d'événement de sorte que les deuxième, troisième, etc. événements sont toujours perdants. Ils n'acquièrent pas le verrou à l'aide des fonctions de verrouillage eventLock() et primaryEventLock().

(5) Ce qui précède peut être vu à la définition des gestionnaires d'événements:

function previousStart(evt){
   // 'race' condition/repetition between 'mousedown' and 'touchstart'
   if(!getEventLock(evt, 'previous'))
      return;

   // a. !!!you have to implement this!!!
   previous(evt.target);

   // b. emulate successive events of this type
   pids.previous = setTimeout(closure, defaults.pressDelay);

   // internal function repeats steps (a), (b)
   function closure(){
      previous(evt.target);
      primaryEventLock(evt, 'previous');
      pids.previous = setTimeout(closure, defaults.pressDelay);
   }
};
function previousEnd(evt){
      clearTimeout(pids.previous);
};

Similaire pour nextStart et nextEnd.

L'idée est que celui qui vient après le premier (tactile ou souris) n'acquiert pas lock à l'aide de function eventLock(evt, key) et s'arrête là.

Le seul moyen d'ouvrir ce verrou consiste à déclencher les gestionnaires d'événements terminaison*End() à l'étape (4): previousEnd et nextEnd.

Je résous également le problème des périphériques tactiles connectés au milieu de la session de manière très intelligente: j'ai remarqué qu'une pression continue plus longue que defaults.pressDelay produisait des appels successifs de la fonction de rappel uniquement pour l'événement principal à ce moment-là ( la raison en est qu'aucun gestionnaire d'événement de fin ne termine l'appelant)!

touchstart event
closure
closure
....
touchend event

Je définis primary le périphérique utilisé par l'utilisateur. Tout ce que vous avez à faire est simplement d'appuyer plus longtemps et immédiatement votre périphérique devient primary à l'aide de primaryEventLock(evt, 'previous') à l'intérieur de la fermeture!

Notez également que le temps nécessaire à l'exécution de previous(event.target) doit être inférieur à defaults.pressDelay.

(6) Enfin, exposons init() à la portée globale:

myScroll.init = init;

Vous devez remplacer l'appel à previous(event.target) par le problème actuel: fiddle .

Notez également qu’à (5b), il existe une solution à une autre question courante comment passer des arguments à une fonction appelée à partir de setTimeout(), c’est-à-dire que setTimeout(previous, defaults.pressDelay) ne dispose pas d’un mécanisme permettant de passer les arguments.

0
centurian

J'utilisais cet assistant jQuery pour lier les événements tactiles et les événements clic.

(function ($) {
$.fn.tclick = function (onclick) {
    this.bind("touchstart", function (e) { onclick.call(this, e); e.stopPropagation(); e.preventDefault(); });
    this.bind("click", function (e) { onclick.call(this, e); });   //substitute mousedown event for exact same result as touchstart         
    return this;
  };
})(jQuery);
0
Joshua