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

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

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

20 ответов

Решение

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

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

var flag = 0;
var element = xxxx;
element.addEventListener("mousedown", function(){
    flag = 0;
}, false);
element.addEventListener("mousemove", function(){
    flag = 1;
}, false);
element.addEventListener("mouseup", function(){
    if(flag === 0){
        console.log("click");
    }
    else if(flag === 1){
        console.log("drag");
    }
}, false);

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

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

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

Очиститель ES2015

let drag = false;

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

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

Если вы уже используете 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);
  });
});

Это должно работать хорошо. Аналогично принятому ответу (хотя и с использованием 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 если вы хотите добавить немного терпимости (то есть обрабатывать крошечные движения как щелчки, а не перетаскивания).

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

Как указывает 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 функция исходит из этого ответа

Это действительно так просто

      var dragged = false
window.addEventListener('mousedown', function () { dragged = false })
window.addEventListener('mousemove', function () { dragged = true })
window.addEventListener('mouseup', function() {
        if (dragged == true) { return }
        console.log("CLICK!! ")
})

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

Просто попробуйте.

Вы можете легко добавить событие, если хотите.

Вы могли сделать это:

         var div = document.getElementById("div");
div.addEventListener("mousedown", function() {
  window.addEventListener("mousemove", drag);
  window.addEventListener("mouseup", lift);
  var didDrag = false;
  function drag() {
    //when the person drags their mouse while holding the mouse button down
    didDrag = true;
    div.innerHTML = "drag"
  }
  function lift() {
    //when the person lifts mouse
    if (!didDrag) {
      //if the person didn't drag
      div.innerHTML = "click";
    } else div.innerHTML = "drag";
    //delete event listeners so that it doesn't keep saying drag
    window.removeEventListener("mousemove", drag)
    window.removeEventListener("mouseup", this)
  }
})
         body {
  outline: none;
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  font-family: Arial, Helvetica, sans-serif;
  overflow: hidden;
}
#div {
  /* calculating -5px for each side of border in case border-box doesn't work */
  width: calc(100vw - 10px);
  height: calc(100vh - 10px);
  border: 5px solid orange;
  background-color: yellow;
  font-weight: 700;
  display: grid;
  place-items: center;
  user-select: none;
  cursor: pointer;
  padding: 0;
  margin: 0;
}
         <html>
  <body>
    <div id="div">Click me or drag me.</div>
  </body>
</html>

Использование 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");
});

Недавно была такая же проблема с древовидным списком, где пользователь может либо щелкнуть элемент, либо перетащить его, что сделало это маленьким Pointer класс и поместите его в мой utils.js

      function Pointer(threshold = 10) {
  let x = 0;
  let y = 0;

  return {
    start(e) {
     x = e.clientX;
     y = e.clientY;
    },

    isClick(e) {
      const deltaX = Math.abs(e.clientX - x);
      const deltaY = Math.abs(e.clientY - y);
      return deltaX < threshold && deltaY < threshold;
    }
  }
}

Здесь вы можете увидеть это в действии:

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

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

Другое решение для ванильного 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);
}

Для публичного действия на карте 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);    

}

Чистый 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);

Основываясь на этом ответе, я сделал это в своем компоненте React:

export default React.memo(() => {
    const containerRef = React.useRef(null);

    React.useEffect(() => {
        document.addEventListener('mousedown', handleMouseMove);

        return () => document.removeEventListener('mousedown', handleMouseMove);
    }, []);

    const handleMouseMove = React.useCallback(() => {
        const drag = (e) => {
            console.log('mouse is moving');
        };

        const lift = (e) => {
            console.log('mouse move ended');
            window.removeEventListener('mousemove', drag);
            window.removeEventListener('mouseup', this);
        };

        window.addEventListener('mousemove', drag);
        window.addEventListener('mouseup', lift);
    }, []);

    return (
        <div style={{ width: '100vw', height: '100vh' }} ref={containerRef} />
    );
})

Следующее кодирование предназначено для обнаружения движения и.

Это будет работать в большинстве случаев. Это также зависит от того, как вы относитесь mouseevent как нажмите.

В JavaScript обнаружение очень просто. Это не касается того, как долго вы нажимаете или перемещаетесь между мышью и мышью. Event.detail не сбрасывается до 1, когда ваша мышь перемещается между mousedown а также mouseup.

Если вам нужно различать щелчок и долгое нажатие, вам нужно проверить разницу в event.timeStamp тоже.

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

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

Очень легко,

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

Другие вопросы по тегам