Comment distinguer le «clic» et le «glisser» de la souris

165

J'utilise jQuery.clickpour gérer l'événement de clic de souris sur le graphique de Raphael, en attendant, je dois gérer l' dragévénement de souris, le glissement de la souris se compose de mousedown, mouseupet mousemovedans Raphael.

Il est difficile de distinguer clicket dragparce qu'il clickcontient également mousedown& mouseup, Comment puis-je distinguer le "clic" de la souris et le "glisser" de la souris puis en Javascript?

Leem
la source

Réponses:

192

Je pense que la différence est qu'il y a un mousemoveentre mousedownet mouseupdans un glisser, mais pas dans un clic.

Vous pouvez faire quelque chose comme ceci:

const element = document.createElement('div')
element.innerHTML = 'test'
document.body.appendChild(element)
let moved
let downListener = () => {
    moved = false
}
element.addEventListener('mousedown', downListener)
let moveListener = () => {
    moved = true
}
element.addEventListener('mousemove', moveListener)
let upListener = () => {
    if (moved) {
        console.log('moved')
    } else {
        console.log('not moved')
    }
}
element.addEventListener('mouseup', upListener)

// release memory
element.removeEventListener('mousedown', downListener)
element.removeEventListener('mousemove', moveListener)
element.removeEventListener('mouseup', upListener)
wong2
la source
38
N'oubliez pas d'exiger un delta minimum X ou Y sur la souris pour déclencher une traînée. Ce serait frustrant d'essayer de cliquer et d'obtenir une opération de glissement à la place en raison d'un retrait de la souris à un tick
Erik Rydgren
12
Je ne pense pas que cela fonctionne plus dans le dernier chrome: 32.0.1700.72 Mousemove se déclenche, que vous déplaciez la souris ou non
mrjrdnthms
17
Ce code de réponse accepté doit inclure une condition de delta minimum entre les coordonnées de la souris XY à mousedownet mouseupau lieu d'écouter l' mousemoveévénement pour définir un indicateur. De plus, cela résoudrait le problème mentionné par @mrjrdnthms
Billybobbonnet
2
J'utilise Chrome 56.0.2924.87 (64 bits) et je ne rencontre pas les problèmes décrits par @mrjrdnthms.
jkupczak
1
@AmerllicA ce n'est probablement pas un bogue mais un comportement attendu, mais vous pouvez regarder les événements mouseenter et mouseleave si cela est intéressant pour votre cas d'utilisation
Rivenfall
37

Si vous utilisez déjà jQuery:

var $body = $('body');
$body.on('mousedown', function (evt) {
  $body.on('mouseup mousemove', function handler(evt) {
    if (evt.type === 'mouseup') {
      // click
    } else {
      // drag
    }
    $body.off('mouseup mousemove', handler);
  });
});
Gustavo Rodrigues
la source
Même si vous déplacez légèrement la souris en cliquant, cela dira drag. Une portée supplémentaire comme d'autres commentaires le disent peut être nécessaire ici.
ChiMo
@ChiMo Ce que je suis utilise est position de stockage de la souris à partir de la première evtet la comparaison avec la position du second evt, ainsi, par exemple: if (evt.type === 'mouseup' || Math.abs(evt1.pageX - evt2.pageX) < 5 || Math.abs(evt1.pageY - evt2.pageY) < 5) { ....
Gustavo Rodrigues
1
J'ai essayé toutes les autres réponses à cette question, et c'est la seule qui a fonctionné lors de la vérification .on('mouseup mousemove touchend touchmove'), et en plus de cela, ne définit pas de variables de position. Excellente solution!
TheThirdMan
Parfois, lorsque je cliquais sur un élément, le "evt.type" renvoyait "mousemove" à la place sur mouseup. Comment puis-je résoudre ce problème?
Libu Mathew
27

Nettoyant ES2015

let drag = false;

document.addEventListener('mousedown', () => drag = false);
document.addEventListener('mousemove', () => drag = true);
document.addEventListener('mouseup', () => console.log(drag ? 'drag' : 'click'));

Je n'ai rencontré aucun bogue, comme d'autres l'ont commenté.

Przemek
la source
6
Cela souffre de clics avec de petits mouvements.
Amir Keibi
1
@AmirKeibi, vous pouvez compter le nombre de sauts de souris (ou même calculer la distance entre les deux clics mais ce serait exagéré)
Rivenfall
19

Cela devrait bien fonctionner. Similaire à la réponse acceptée (bien qu'en utilisant jQuery), mais l' isDraggingindicateur n'est réinitialisé que si la nouvelle position de la souris diffère de celle sur l' mousedownévénement. Contrairement à la réponse acceptée, cela fonctionne sur les versions récentes de Chrome, où mousemovese déclenche, que la souris ait été déplacée ou non.

var isDragging = false;
var startingPos = [];
$(".selector")
    .mousedown(function (evt) {
        isDragging = false;
        startingPos = [evt.pageX, evt.pageY];
    })
    .mousemove(function (evt) {
        if (!(evt.pageX === startingPos[0] && evt.pageY === startingPos[1])) {
            isDragging = true;
        }
    })
    .mouseup(function () {
        if (isDragging) {
            console.log("Drag");
        } else {
            console.log("Click");
        }
        isDragging = false;
        startingPos = [];
    });

Vous pouvez également ajuster la vérification des coordonnées mousemovesi vous souhaitez ajouter un peu de tolérance (c'est-à-dire traiter les petits mouvements comme des clics et non des traînées).

nirvana-msu
la source
12

Si vous avez envie d'utiliser Rxjs :

var element = document;

Rx.Observable
  .merge(
    Rx.Observable.fromEvent(element, 'mousedown').mapTo(0),
    Rx.Observable.fromEvent(element, 'mousemove').mapTo(1)
  )
  .sample(Rx.Observable.fromEvent(element, 'mouseup'))
  .subscribe(flag => {
      console.clear();
      console.log(flag ? "drag" : "click");
  });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://unpkg.com/@reactivex/[email protected]/dist/global/Rx.js"></script>

Ceci est un clone direct de ce que @ wong2 a fait dans sa réponse, mais converti en RxJs.

Utilisation également intéressante de sample. L' sampleopérateur prendra la dernière valeur de la source (le mergeof mousedownet mousemove) et l'émettra lorsque l'observable interne ( mouseup) émettra.

Dorus
la source
22
J'écris tout mon code avec des observables pour que mon patron ne puisse pas embaucher quelqu'un d'autre pour me remplacer.
Reactgular
11

Comme le souligne mrjrdnthms dans son commentaire sur la réponse acceptée, cela ne fonctionne plus sur Chrome (il déclenche toujours la suppression de la souris), j'ai adapté la réponse de Gustavo (puisque j'utilise jQuery) pour traiter le comportement de Chrome.

var currentPos = [];

$(document).on('mousedown', function (evt) {

   currentPos = [evt.pageX, evt.pageY]

  $(document).on('mousemove', function handler(evt) {

    currentPos=[evt.pageX, evt.pageY];
    $(document).off('mousemove', handler);

  });

  $(document).on('mouseup', function handler(evt) {

    if([evt.pageX, evt.pageY].equals(currentPos))
      console.log("Click")
    else
      console.log("Drag")

    $(document).off('mouseup', handler);

  });

});

La Array.prototype.equalsfonction vient de cette réponse

Francisco Aquino
la source
1
Cela a presque fonctionné pour moi, mais j'ai eu une erreur de la [evt.pageX, evt.pageY].equals()commande. J'ai remplacé cela par (evt.pageX === currentPos[0] && evt.pageY===currentPos[1]), et tout était bon. :)
user2441511
Le equalscode doit être ajouté à partir du lien au bas de mon message
Francisco Aquino
Ah, ça l'explique. Merci.
user2441511
1
Je n'arrive pas à comprendre la logique. Pourquoi mettre à jour vous currentPossur mousemove? Cela ne signifie-t-il pas que vous traiteriez certains traînées comme des clics?
nirvana-msu
1
Cela ne se déclenche pas si vous "mouseup"déplacez toujours la souris.
ChiMo
9

Toutes ces solutions s'interrompent avec de minuscules mouvements de souris ou sont trop compliquées.

Voici une solution simple et adaptable utilisant deux écouteurs d'événements. Delta est la distance en pixels que vous devez déplacer horizontalement ou verticalement entre les événements haut et bas pour que le code le classe comme un glissement plutôt que comme un clic. C'est parce que parfois vous déplacerez la souris ou votre doigt de quelques pixels avant de le soulever.

const delta = 6;
let startX;
let startY;

element.addEventListener('mousedown', function (event) {
  startX = event.pageX;
  startY = event.pageY;
});

element.addEventListener('mouseup', function (event) {
  const diffX = Math.abs(event.pageX - startX);
  const diffY = Math.abs(event.pageY - startY);

  if (diffX < delta && diffY < delta) {
    // Click!
  }
});
andreyrd
la source
De loin la meilleure réponse!
Giorgio Tempesta
Salut @andreyrd, puis-je savoir ce qui deltaest utilisé pour cela? c'est quelque chose à voir avec le robinet dans un appareil mobile?
Haziq
1
@Haziq Je pense que les personnes mentionnées dans les commentaires des meilleures solutions deltasont utilisées pour "Ce serait frustrant d'essayer de cliquer et d'obtenir une opération de glissement à la place en raison d'une souris à un tick"
Michael Bykhovtsev
1
J'ai mis à jour la réponse avec une explication. En gros, si votre doigt a moins de 6 pixels, il comptera toujours comme un clic. S'il se déplace de 6 pixels ou plus, il comptera comme un glissement.
andreyrd le
5

Utilisation de jQuery avec une prise de 5 pixels x / y pour détecter la traînée:

var dragging = false;
$("body").on("mousedown", function(e) {
  var x = e.screenX;
  var y = e.screenY;
  dragging = false;
  $("body").on("mousemove", function(e) {
    if (Math.abs(x - e.screenX) > 5 || Math.abs(y - e.screenY) > 5) {
      dragging = true;
    }
  });
});
$("body").on("mouseup", function(e) {
  $("body").off("mousemove");
  console.log(dragging ? "drag" : "click");
});
vent d'argent
la source
2

Si juste pour filtrer le cas de glissement, faites-le comme ceci:

var moved = false;
$(selector)
  .mousedown(function() {moved = false;})
  .mousemove(function() {moved = true;})
  .mouseup(function(event) {
    if (!moved) {
        // clicked without moving mouse
    }
  });
jqgsninimo
la source
1

Pure JS avec DeltaX et DeltaY

Ce DeltaX et DeltaY comme suggéré par un commentaire dans la réponse acceptée pour éviter l'expérience frustrante lorsque vous essayez de cliquer et d'obtenir une opération de glissement à la place en raison d'un déplacement de la souris à un tick.

    deltaX = deltaY = 2;//px
    var element = document.getElementById('divID');
    element.addEventListener("mousedown", function(e){
        if (typeof InitPageX == 'undefined' && typeof InitPageY == 'undefined') {
            InitPageX = e.pageX;
            InitPageY = e.pageY;
        }

    }, false);
    element.addEventListener("mousemove", function(e){
        if (typeof InitPageX !== 'undefined' && typeof InitPageY !== 'undefined') {
            diffX = e.pageX - InitPageX;
            diffY = e.pageY - InitPageY;
            if (    (diffX > deltaX) || (diffX < -deltaX)
                    || 
                    (diffY > deltaY) || (diffY < -deltaY)   
                    ) {
                console.log("dragging");//dragging event or function goes here.
            }
            else {
                console.log("click");//click event or moving back in delta goes here.
            }
        }
    }, false);
    element.addEventListener("mouseup", function(){
        delete InitPageX;
        delete InitPageY;
    }, false);

   element.addEventListener("click", function(){
        console.log("click");
    }, false);
Waqas Bukhary
la source
1

Pour une action publique sur une carte OSM (positionner un marqueur sur clic), la question était: 1) comment déterminer la durée de la souris vers le bas -> vers le haut (vous ne pouvez pas imaginer créer un nouveau marqueur pour chaque clic) et 2) le déplacement de la souris vers le bas -> vers le haut (c'est-à-dire que l'utilisateur fait glisser la carte).

const map = document.getElementById('map');

map.addEventListener("mousedown", position); 
map.addEventListener("mouseup", calculate);

let posX, posY, endX, endY, t1, t2, action;

function position(e) {

  posX = e.clientX;
  posY = e.clientY;
  t1 = Date.now();

}

function calculate(e) {

  endX = e.clientX;
  endY = e.clientY;
  t2 = (Date.now()-t1)/1000;
  action = 'inactive';

  if( t2 > 0.5 && t2 < 1.5) { // Fixing duration of mouse down->up

      if( Math.abs( posX-endX ) < 5 && Math.abs( posY-endY ) < 5 ) { // 5px error on mouse pos while clicking
         action = 'active';
         // --------> Do something
      }
  }
  console.log('Down = '+posX + ', ' + posY+'\nUp = '+endX + ', ' + endY+ '\nAction = '+ action);    

}
Wolden
la source
0

Une autre solution utilisant pour le JS vanille basé sur les classes utilisant un seuil de distance

private initDetectDrag(element) {
    let clickOrigin = { x: 0, y: 0 };
    const dragDistanceThreshhold = 20;

    element.addEventListener('mousedown', (event) => {
        this.isDragged = false
        clickOrigin = { x: event.clientX, y: event.clientY };
    });
    element.addEventListener('mousemove', (event) => {
        if (Math.sqrt(Math.pow(clickOrigin.y - event.clientY, 2) + Math.pow(clickOrigin.x - event.clientX, 2)) > dragDistanceThreshhold) {
            this.isDragged = true
        }
    });
}

Et ajoutez à la classe (SOMESLIDER_ELEMENT peut également être un document pour être global):

private isDragged: boolean;
constructor() {
    this.initDetectDrag(SOMESLIDER_ELEMENT);
    this.doSomeSlideStuff(SOMESLIDER_ELEMENT);
    element.addEventListener('click', (event) => {
        if (!this.sliderIsDragged) {
            console.log('was clicked');
        } else {
            console.log('was dragged, ignore click or handle this');
        }
    }, false);
}
Tim Rasim
la source
0

Si vous souhaitez vérifier le comportement de clic ou de glissement d'un élément spécifique, vous pouvez le faire sans avoir à écouter le corps.

$(document).ready(function(){
  let click;
  
  $('.owl-carousel').owlCarousel({
    items: 1
  });
  
  // prevent clicks when sliding
  $('.btn')
    .on('mousemove', function(){
      click = false;
    })
    .on('mousedown', function(){
      click = true;
    });
    
  // change mouseup listener to '.content' to listen to a wider area. (mouse drag release could happen out of the '.btn' which we have not listent to). Note that the click will trigger if '.btn' mousedown event is triggered above
  $('.btn').on('mouseup', function(){
    if(click){
      $('.result').text('clicked');
    } else {
      $('.result').text('dragged');
    }
  });
});
.content{
  position: relative;
  width: 500px;
  height: 400px;
  background: #f2f2f2;
}
.slider, .result{
  position: relative;
  width: 400px;
}
.slider{
  height: 200px;
  margin: 0 auto;
  top: 30px;
}
.btn{
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  height: 100px;
  background: #c66;
}
.result{
  height: 30px;
  top: 10px;
  text-align: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/OwlCarousel2/2.3.4/owl.carousel.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/OwlCarousel2/2.3.4/assets/owl.carousel.min.css" />
<div class="content">
  <div class="slider">
    <div class="owl-carousel owl-theme">
      <div class="item">
        <a href="#" class="btn" draggable="true">click me without moving the mouse</a>
      </div>
      <div class="item">
        <a href="#" class="btn" draggable="true">click me without moving the mouse</a>
      </div>
    </div>
    <div class="result"></div>
  </div>
  
</div>

Lasithds
la source
0

de la réponse de @Przemek,

function listenClickOnly(element, callback, threshold=10) {
  let drag = 0;
  element.addEventListener('mousedown', () => drag = 0);
  element.addEventListener('mousemove', () => drag++);
  element.addEventListener('mouseup', e => {
    if (drag<threshold) callback(e);
  });
}

listenClickOnly(
  document,
  () => console.log('click'),
  10
);

Jehong Ahn
la source