Как различить «щелчок мышью» и «перетаскивание»

165

Я использую jQuery.clickдля обработки события щелчка мыши на графике Рафаэля, а мне нужно обработать dragсобытие мыши, состоящее из перетаскивания мыши mousedown, mouseupи mousemoveв Рафаэле.

Это трудно отличить, clickи dragпотому что clickтакже содержат mousedown& mouseup, Как я могу различить "щелчок мышью" и "перетаскивание мышью" тогда в Javascript?

Лим
источник

Ответы:

192

Я думаю, что разница в том, что mousemoveмежду mousedownи mouseupв перетаскивании есть, но не в щелчке.

Вы можете сделать что-то вроде этого:

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
источник
38
Просто не забудьте потребовать минимальное дельта X или Y для перемещения мыши. Было бы неприятно пытаться щелкнуть мышью и получить операцию перетаскивания из-за однократного перемещения мышью
Эрик Ридгрен
12
Я не думаю, что это работает больше в последнем Chrome: 32.0.1700.72 Mousemove срабатывает независимо от того, двигаете ли вы мышь или нет
mrjrdnthms
17
Этот принятый код ответа должен включать минимальное дельта-условие между координатами мыши XY mousedownи mouseupвместо прослушивания mousemoveсобытия, чтобы установить флаг. Кроме того, это решило бы проблему, упомянутую @mrjrdnthms
Billybobbonnet
2
Я использую Chrome 56.0.2924.87 (64-разрядная версия) и не испытываю проблем, описываемых @mrjrdnthms.
jkupczak
1
@AmerllicA это, вероятно, не ошибка, а ожидаемое поведение, однако вы можете наблюдать события mouseenter и mouseleave, если это интересно для вашего случая использования
Rivenfall
37

Если вы уже используете 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);
  });
});
Густаво Родригес
источник
Даже если вы слегка передвинете мышь, нажимая, это скажет drag. Дополнительная область, как и другие комментарии, может потребоваться здесь.
ChiMo
@ChiMo Что я использую это место хранения мыши из первой evtи сравнения с положением второго evt, так, например: if (evt.type === 'mouseup' || Math.abs(evt1.pageX - evt2.pageX) < 5 || Math.abs(evt1.pageY - evt2.pageY) < 5) { ....
Густаво Родригес
1
Я попробовал все остальные ответы на этот вопрос, и это единственный, который работал при проверке .on('mouseup mousemove touchend touchmove'), и, кроме того, не устанавливает переменные позиции. Отличное решение!
TheThirdMan
Иногда, когда я нажимал на элемент, «evt.type» возвращал «mousemove» вместо mouseup. Как я могу решить эту проблему?
Либу Мэтью
27

Очиститель ES2015

let drag = false;

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

Не было никаких ошибок, как другие комментируют.

Przemek
источник
6
Это страдает от щелчков с крошечными движениями.
Амир Кейби
1
@AmirKeibi Вы могли бы посчитать количество движений мыши (или даже вычислить расстояние между двумя щелчками, но это было бы излишним)
Rivenfall
19

Это должно работать хорошо. Аналогичен принятому ответу (хотя и с использованием jQuery), но isDraggingфлаг сбрасывается только в том случае, если новая позиция мыши отличается от позиции при mousedownсобытии. В отличие от принятого ответа, это работает в последних версиях Chrome, где mousemoveзапускается независимо от того, была ли перемещена мышь или нет.

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

Вы также можете настроить проверку координат, mousemoveесли хотите добавить немного допусков (то есть обрабатывать крошечные движения как щелчки, а не перетаскивания).

нирвана-MSU
источник
12

Если вы хотите использовать 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>

Это прямой клон того, что @ wong2 сделал в своем ответе, но преобразовал в RxJs.

Также интересное использование sample. sampleОператор примет последнее значение из источника ( mergeиз mousedownи mousemove) и излучает его , когда внутренние наблюдаемый ( mouseup) испускает.

Dorus
источник
22
Я пишу весь свой код с помощью наблюдаемых, чтобы мой начальник не мог нанять кого-то другого, чтобы заменить меня.
Reactgular
11

Как указывает mrjrdnthms в своем комментарии о принятом ответе, это больше не работает в Chrome (он всегда запускает перемещение мыши), я адаптировал ответ Густаво (поскольку я использую jQuery) для учета поведения 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);

  });

});

Array.prototype.equalsФункция исходит из этого ответа

Франциско Акино
источник
1
Это почти сработало для меня, но я получил ошибку от [evt.pageX, evt.pageY].equals()команды. Я заменил это на (evt.pageX === currentPos[0] && evt.pageY===currentPos[1]), и все было хорошо. :)
user2441511 9.09.16
В equalsпотребности кода должны быть добавлена по ссылке в нижней части моего поста
Francisco Акино
Ах, это объясняет это. Спасибо.
user2441511 12.09.16
1
Я не могу понять логику. Почему вы обновляетесь currentPosна mousemove? Разве это не значит, что вы бы воспринимали некоторые перетаскивания как клики?
нирвана-мсу
1
Это не срабатывает, если вы "mouseup"все еще двигаете мышь.
ChiMo
9

Все эти решения либо ломаются от крошечных движений мыши, либо слишком сложны.

Вот простое адаптируемое решение с использованием двух слушателей событий. Дельта - это расстояние в пикселях, которое вы должны перемещать горизонтально или вертикально между событиями вверх и вниз, чтобы код классифицировал его как перетаскивание, а не щелчок. Это потому, что иногда вы будете двигать мышь или палец на несколько пикселей, прежде чем поднимать его.

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
источник
Безусловно лучший ответ!
Джорджио Темпеста
Привет @andreyrd, я могу знать, что deltaиспользуется для этого? это как-то связано с тапом в мобильном устройстве?
Хазик
1
@Haziq Я думаю, что люди, упомянутые в комментариях о лучших решениях delta, используются для «Было бы неприятно пытаться щелкнуть мышью и получить операцию перетаскивания из-за однократного перемещения мышью»
Михаил Быховцев,
1
Я обновил ответ объяснением. В основном, если ваш палец меньше 6 пикселей, он все равно будет считаться кликом. Если он перемещается на 6 или более пикселей, он будет считаться перетаскиванием.
Андрей
5

Использование jQuery с 5-пиксельным х / у-полем для определения сопротивления:

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");
});
Silverwind
источник
2

Если просто отфильтровать случай перетаскивания, сделайте это так:

var moved = false;
$(selector)
  .mousedown(function() {moved = false;})
  .mousemove(function() {moved = true;})
  .mouseup(function(event) {
    if (!moved) {
        // clicked without moving mouse
    }
  });
jqgsninimo
источник
1

Чистый JS с DeltaX и DeltaY

Это DeltaX и DeltaY, как предлагается в комментарии в принятом ответе, чтобы избежать неприятных ощущений при попытке щелкнуть мышью и получить операцию перетаскивания вместо этого из-за перемещения мыши в один тик.

    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

Для публичного действия на карте OSM (поместите маркер на клик) вопрос был: 1) как определить продолжительность нажатия мыши вниз -> вверх (вы не можете себе представить создание нового маркера для каждого клика) и 2) сделал мышь перемещается вниз -> вверх (т.е. пользователь перетаскивает карту).

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

Другое решение, использующее для класса ванильный JS, использующий порог расстояния

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

И добавьте в класс (SOMESLIDER_ELEMENT также может быть документом, который будет глобальным):

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

Если вы хотите проверить поведение щелчка или перетаскивания определенного элемента, вы можете сделать это без прослушивания тела.

$(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
источник
0

из ответа @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
);

Джехонг Ан
источник