如何区分鼠标的“单击”和“拖动”


165

我使用的jQuery.click处理上拉斐尔图形的鼠标点击事件,同时,我需要处理鼠标drag事件,鼠标拖动由mousedownmouseupmousemove在拉斐尔。

很难区分click并且drag因为click还包含mousedownmouseup,然后如何在Javascript中区分鼠标“ click”和鼠标“ drag”呢?

Answers:


192

我觉得不同的是,有一mousemovemousedown,并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)

38
只需记住在鼠标移动时要求最小增量X或Y即可触发拖动。试图单击并进行拖动操作会令人沮丧,因为鼠标移动了一个刻度线
Erik Rydgren 2011年

12
我认为在最新的Chrome浏览器中此功能不再有效:32.0.1700.72无论是否移动鼠标,Mousemove都会触发
mrjrdnthms 2014年

17
此可接受的答案代码应包含XY鼠标坐标处的最小增量条件,mousedownmouseup不是监听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”会在“ mouseup”上返回“ mousemove”。我该如何解决这个问题?
Libu Mathew

27

清洁剂ES2015

let drag = false;

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

正如其他人所说,没有遇到任何错误。


6
这是因为点击动作很小。
阿米尔·基比

1
@AmirKeibi,您可以算出鼠标移动的数量(甚至可以计算两次点击之间的距离,但这可能会导致过大杀伤力)
Rivenfall

19

这应该运作良好。与接受的答案类似(尽管使用jQuery),但是isDragging仅当新鼠标位置与mousedown事件上的鼠标位置不同时,才重置标志。与公认的答案不同,该答案适用于最新版本的Chrome,mousemove无论鼠标是否移动,都会在最新版本的Chrome上触发。

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如果您想增加一点公差(例如,将微小的移动视为点击而不是拖动),则也可以调整坐标检查。


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操作者将采取的最新值从源(mergemousedownmousemove),并发射它的内观察到的(当mouseup)发射。


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

equals从链接加入我的帖子底部代码的需求
弗朗西斯科·阿基诺

嗯,这可以解释。谢谢。
user2441511

1
我似乎无法理解逻辑。为什么你更新currentPosmousemove?这是否意味着您会将某些拖动视为点击?
nirvana-msu

1
如果您"mouseup"仍然移动鼠标,则不会触发。
ChiMo

9

所有这些解决方案要么因鼠标的微小移动而中断,要么过于复杂。

这是使用两个事件侦听器的简单可调整解决方案。Delta是您必须在上下事件之间在水平或垂直方向上移动的距离(以像素为单位),以便代码将其分类为拖动而不是单击。这是因为有时您在抬起鼠标或手指之前会将其移动几个像素。

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

迄今为止最好的答案!
Giorgio Tempesta

嗨@andreyrd,我可以知道这delta是用来干什么的吗?这与移动设备中的点击有关吗?
Haziq

@Haziq我认为,正如人们在最佳解决方案的评论中提到的那样delta,“一键鼠标移动,将令人沮丧地尝试单击并进行拖动操作”
Michael Bykhovtsev

我用解释更新了答案。基本上,如果您的手指小于6像素,它仍会算作一次点击。如果它移动6个或更多像素,它将算作拖动。
安德烈

5

使用具有5个像素x / y阈值的jQuery来检测拖动:

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

如果只是为了过滤拖曳情况,请按照以下步骤操作:

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

1

带有DeltaX和DeltaY的纯JS

如已接受答案中的注释所建议的,此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>


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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.