Cómo distinguir el mouse "hacer clic" y "arrastrar"

165

Yo uso jQuery.clickpara controlar el evento clic del ratón sobre el gráfico Rafael, por su parte, que necesito para manejar el ratón dragcaso, arrastrar el ratón consiste en mousedown, mouseupy mousemoveen la de Rafael.

Es difícil de distinguir clicky dragporque clicktambién contiene mousedown& mouseup, ¿Cómo puedo distinguir el mouse "clic" y el mouse "arrastrar" luego en Javascript?

Leem
fuente

Respuestas:

192

Creo que la diferencia es que hay un mousemovemedio mousedowny mouseupun arrastre, pero no en un clic.

Puedes hacer algo como esto:

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
fuente
38
Solo recuerde requerir un mínimo delta X o Y en el movimiento del mouse para activar un arrastre. Sería frustrante tratar de hacer clic y obtener una operación de arrastre en su lugar debido a un movimiento del mouse de un solo clic
Erik Rydgren
12
No creo que esto funcione más en el último cromo: 32.0.1700.72 Mousemove se dispara si mueves el mouse o no
mrjrdnthms
17
Este código de respuesta aceptado debe incluir una condición delta mínima entre las coordenadas XY del mouse en mousedowny en mouseuplugar de escuchar el mousemoveevento para establecer una bandera. Además, solucionaría el problema mencionado por @mrjrdnthms
Billybobbonnet
2
Estoy ejecutando Chrome 56.0.2924.87 (64 bits) y no estoy experimentando los problemas que describe @mrjrdnthms.
jkupczak
1
@AmerllicA esto probablemente no sea un error, sino un comportamiento esperado, sin embargo, podría ver los eventos mouseenter y mouseleave si eso es interesante para su caso de uso
Rivenfall
37

En caso de que ya estés usando 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
fuente
Incluso si mueve el mouse un poco mientras hace clic, esto dirá drag. Un alcance adicional como dicen otros comentarios puede ser necesario aquí.
ChiMo
@ChiMo Lo que estoy usando es el almacenamiento de estado posición del ratón desde la primera evty la comparación con la posición de la segunda evt, por lo que, por ejemplo: if (evt.type === 'mouseup' || Math.abs(evt1.pageX - evt2.pageX) < 5 || Math.abs(evt1.pageY - evt2.pageY) < 5) { ....
Gustavo Rodrigues
1
Intenté todas las otras respuestas a esta pregunta, y esta es la única que funcionó al verificar .on('mouseup mousemove touchend touchmove'), y además no establece variables de posición. Gran solución!
TheThirdMan
A veces, cuando hago clic en un elemento, el "evt.type" devuelve "mousemove" en lugar de mouseup. ¿Cómo puedo resolver ese problema?
Libu Mathew
27

Cleaner ES2015

let drag = false;

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

No experimenté ningún error, como otros comentan.

Przemek
fuente
66
Esto sufre clics con pequeños movimientos.
Amir Keibi
1
@AmirKeibi, podría contar la cantidad de movimientos del mouse (o incluso calcular la distancia entre los dos clics, pero eso sería excesivo)
Rivenfall
19

Esto debería funcionar bien. Similar a la respuesta aceptada (aunque usando jQuery), pero el isDraggingindicador solo se restablece si la nueva posición del mouse difiere de la del mousedownevento. A diferencia de la respuesta aceptada, eso funciona en versiones recientes de Chrome, donde mousemovese dispara independientemente de si el mouse se movió o no.

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

También puede ajustar la verificación de coordenadas mousemovesi desea agregar un poco de tolerancia (es decir, trate los movimientos pequeños como clics, no como arrastre).

nirvana-msu
fuente
12

Si tienes ganas de usar 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>

Este es un clon directo de lo que @ wong2 hizo en su respuesta, pero se convirtió a RxJs.

También uso interesante de sample. El sampleoperador tomará el último valor de la fuente (el mergede mousedowny mousemove) y lo emitirá cuando se emita el observable interno ( mouseup).

Dorus
fuente
22
Escribo todo mi código con observables para que mi jefe no pueda contratar a otra persona para que me reemplace.
Reactgular
11

Como mrjrdnthms señala en su comentario sobre la respuesta aceptada, esto ya no funciona en Chrome (siempre activa el mousemove), he adaptado la respuesta de Gustavo (ya que estoy usando jQuery) para abordar el comportamiento 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.equalsfunción proviene de esta respuesta

Francisco Aquino
fuente
1
Esto casi funcionó para mí, pero recibí un error del [evt.pageX, evt.pageY].equals()comando. Reemplacé eso con (evt.pageX === currentPos[0] && evt.pageY===currentPos[1]), y todo estuvo bien. :)
user2441511
El equalscódigo debe agregarse desde el enlace en la parte inferior de mi publicación
Francisco Aquino
Ah, eso lo explica. Gracias.
user2441511
1
Parece que no puedo entender la lógica. ¿Por qué actualizar currentPosel mousemove? ¿No significa esto que tratarías algunos arrastres como clics?
nirvana-msu
1
Esto no se dispara si "mouseup"aún mueves el mouse.
ChiMo
9

Todas estas soluciones se rompen con pequeños movimientos del mouse o son demasiado complicadas.

Aquí hay una solución simple y adaptable que utiliza dos oyentes de eventos. Delta es la distancia en píxeles que debe mover horizontal o verticalmente entre los eventos arriba y abajo para que el código lo clasifique como un arrastre en lugar de un clic. Esto se debe a que a veces moverá el mouse o el dedo unos pocos píxeles antes de levantarlo.

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
fuente
De lejos, la mejor respuesta!
Giorgio Tempesta
Hola @andreyrd, ¿puedo saber qué deltase usa para esto? Qué tiene que ver con tocar en un dispositivo móvil?
Haziq
1
@Haziq Creo que, como la gente mencionada en los comentarios de las mejores soluciones, deltase usa para "Sería frustrante intentar hacer clic y obtener una operación de arrastre en su lugar debido a un movimiento del mouse de una marca"
Michael Bykhovtsev
1
Actualicé la respuesta con una explicación. Básicamente, si su dedo tiene menos de 6 píxeles, seguirá contando como un clic. Si se mueve 6 o más píxeles, contará como un arrastre.
andreyrd
5

Usando jQuery con 5 píxeles x / y theshold para detectar el arrastre:

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");
});
viento de plata
fuente
2

Si solo quiere filtrar el caso de arrastre, hágalo así:

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

Pure JS con DeltaX y DeltaY

Este DeltaX y DeltaY como lo sugiere un comentario en la respuesta aceptada para evitar la experiencia frustrante al intentar hacer clic y obtener una operación de arrastre debido a un movimiento del mouse de un solo clic.

    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
fuente
1

Para una acción pública en un mapa OSM (coloque un marcador al hacer clic), la pregunta era: 1) cómo determinar la duración del mouse hacia abajo-> arriba (no se puede imaginar crear un nuevo marcador para cada clic) y 2) el mouse se mueve durante abajo-> arriba (es decir, el usuario está arrastrando el mapa).

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
fuente
0

Otra solución que usa para vanilla JS basada en clases usando un umbral de distancia

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

Y agregue a la clase (SOMESLIDER_ELEMENT también puede ser documento para ser 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
fuente
0

Si desea verificar el comportamiento de clic o arrastre de un elemento específico, puede hacerlo sin tener que escuchar el cuerpo.

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

de la respuesta 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
fuente