Come distinguere "clic" e "trascinamento" del mouse


165

Io uso jQuery.clickper gestire l'evento clic del mouse sul grafico Raffaello, nel frattempo, ho bisogno di gestire il mouse dragevento, trascinare il mouse è costituito da mousedown, mouseupe mousemovein Raffaello.

È difficile distinguerlo clicke dragperché clickcontiene anche mousedown& mouseup, Come posso distinguere il mouse "clic" e il mouse "trascina" quindi in Javascript?

Risposte:


192

Penso che la differenza sia che c'è un mousemovetra mousedowne mouseupin un trascinamento, ma non in un clic.

Puoi fare qualcosa del genere:

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)

38
Ricorda solo di richiedere un delta minimo X o Y su mousemove per attivare un trascinamento. Sarebbe frustrante provare a fare clic e ottenere un'operazione di trascinamento invece a causa di un mousemove di un segno di spunta
Erik Rydgren,

12
Non credo che funzioni più nell'ultimo chrome: 32.0.1700.72 Mousemove si attiva indipendentemente dal fatto che si sposti o meno il mouse
mrjrdnthms,

17
Questo codice di risposta accettato dovrebbe includere una condizione delta minima tra le coordinate del mouse XY su mousedowne mouseupinvece di ascoltare l' mousemoveevento per impostare un flag. Inoltre, risolverebbe il problema menzionato da @mrjrdnthms
Billybobbonnet,

2
Sto eseguendo Chrome 56.0.2924.87 (64 bit) e non sto riscontrando i problemi che @mrjrdnthms sta descrivendo.
jkupczak,

1
@AmerllicA questo probabilmente non è un bug ma un comportamento previsto, tuttavia potresti guardare il mouseenter e gli eventi di mouseleave se questo è interessante per il tuo caso d'uso
Rivenfall,

37

Nel caso in cui tu stia già utilizzando 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);
  });
});

Anche se muovi leggermente il mouse mentre fai clic, questo dirà drag. Un ambito extra come altri commenti stanno dicendo potrebbe essere necessario qui.
ChiMo,

@ChiMo Quello che sto usando è la memorizzazione di posizione del mouse dalla prima evte confrontando con la posizione del secondo evt, così, per esempio: if (evt.type === 'mouseup' || Math.abs(evt1.pageX - evt2.pageX) < 5 || Math.abs(evt1.pageY - evt2.pageY) < 5) { ....
Gustavo Rodrigues,

1
Ho provato tutte le altre risposte a questa domanda, e questa è l'unica che ha funzionato durante il controllo .on('mouseup mousemove touchend touchmove'), e soprattutto non imposta variabili di posizione. Ottima soluzione!
TheThirdMan,

A volte, quando ho fatto clic su un elemento, "evt.type" restituisce "mousemove" anziché al passaggio del mouse. Come posso risolvere quel problema?
Libu Mathew,

27

Detergente ES2015

let drag = false;

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

Non ho riscontrato alcun bug, come altri commentano.


6
Questo soffre di clic con piccole mosse.
Amir Keibi,

1
@AmirKeibi potresti contare il numero di tappetini per mouse (o addirittura calcolare la distanza tra i due clic ma sarebbe eccessivo)
Rivenfall,

19

Questo dovrebbe funzionare bene. Simile alla risposta accettata (sebbene utilizzi jQuery), ma il isDraggingflag viene ripristinato solo se la nuova posizione del mouse differisce da quella mousedowndell'evento attivo. A differenza della risposta accettata, che funziona su versioni recenti di Chrome, dove mousemoveviene attivato indipendentemente dal fatto che il mouse sia stato spostato o meno.

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 = [];
    });

Puoi anche regolare il controllo delle coordinate mousemovese vuoi aggiungere un po 'di tolleranza (cioè trattare piccoli movimenti come clic, non trascinamenti).


12

Se hai voglia di usare 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/rxjs@5.4.1/dist/global/Rx.js"></script>

Questo è un clone diretto di ciò che @ wong2 ha fatto nella sua risposta, ma convertito in RxJs.

Interessante anche l'uso di sample. L' sampleoperatore prenderà l'ultimo valore dalla fonte (il mergedi mousedowne mousemove) e lo emetterà quando emette l'osservabile interno ( mouseup).


22
Scrivo tutto il mio codice con osservabili in modo che il mio capo non possa assumere qualcun altro per sostituirmi.
Reactgular

11

Come sottolinea mrjrdnthms nel suo commento sulla risposta accettata, questo non funziona più su Chrome (attiva sempre il mouse), ho adattato la risposta di Gustavo (dal momento che sto usando jQuery) per affrontare il comportamento di 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.equalsfunzione deriva da questa risposta


1
Questo ha funzionato quasi per me, ma ho ricevuto un errore dal [evt.pageX, evt.pageY].equals()comando. L'ho sostituito con (evt.pageX === currentPos[0] && evt.pageY===currentPos[1]), e tutto è andato bene. :)
user2441511

Il equalscodice deve essere aggiunto dal link in fondo al mio post
Francisco Aquino,

Ah, questo lo spiega. Grazie.
user2441511

1
Non riesco a capire la logica. Perché si aggiorna currentPossu mousemove? Questo non significa che tratteresti alcuni trascinamenti come clic?
nirvana-msu,

1
Questo non si attiva se si "mouseup"sta ancora spostando il mouse.
ChiMo,

9

Tutte queste soluzioni si rompono su piccoli movimenti del mouse o sono eccessivamente complicate.

Ecco una semplice soluzione adattabile che utilizza due listener di eventi. Delta è la distanza in pixel che è necessario spostare orizzontalmente o verticalmente tra gli eventi su e giù affinché il codice lo classifichi come un trascinamento piuttosto che un clic. Questo perché a volte si sposta il mouse o il dito di alcuni pixel prima di sollevarlo.

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!
  }
});

Di gran lunga la migliore risposta!
Giorgio Tempesta,

Ciao @andreyrd, posso sapere a cosa deltaserve? ha a che fare con il tocco nel dispositivo mobile?
Haziq,

1
@Haziq Penso che quando le persone menzionate nei commenti sulle migliori soluzioni deltavengono utilizzate per "Sarebbe frustrante provare a fare clic e ottenere un'operazione di trascinamento invece di un mousemove di un segno di spunta"
Michael Bykhovtsev

1
Ho aggiornato la risposta con una spiegazione. Fondamentalmente se il dito è inferiore a 6 pixel, verrà comunque conteggiato come un clic. Se si sposta di 6 o più pixel, verrà conteggiato come un trascinamento.
Andreyrd,

5

Utilizzo di jQuery con un theshold x / y a 5 pixel per rilevare il trascinamento:

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");
});

2

Se solo per filtrare il caso di trascinamento, fallo in questo modo:

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

1

Pure JS con DeltaX e DeltaY

Questo DeltaX e DeltaY come suggerito da un commento nella risposta accettata per evitare l'esperienza frustrante quando si tenta di fare clic e ottenere un'operazione di trascinamento invece a causa di un mousemove di un segno di spunta.

    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);

1

Per un'azione pubblica su una mappa OSM (posiziona un marcatore su clic) la domanda era: 1) come determinare la durata del mouse verso il basso-> su (non puoi immaginare di creare un nuovo marcatore per ogni clic) e 2) ha fatto il mouse si sposta durante il down-> up (ovvero l'utente sta trascinando la mappa).

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);    

}

0

Un'altra soluzione che utilizza per la classe basata su vaniglia JS utilizzando una soglia di distanza

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
        }
    });
}

E aggiungi alla classe (SOMESLIDER_ELEMENT può anche essere un documento globale):

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);
}

0

Se vuoi controllare il comportamento del clic o del trascinamento di un elemento specifico, puoi farlo senza dover ascoltare il corpo.

$(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>


0

dalla risposta di @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
);

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.