如何区分鼠标“点击”和“拖动”
Posted
技术标签:
【中文标题】如何区分鼠标“点击”和“拖动”【英文标题】:How to distinguish mouse "click" and "drag" 【发布时间】:2011-08-27 21:13:58 【问题描述】:我使用jQuery.click
来处理Raphael图上的鼠标点击事件,同时,我需要处理鼠标drag
事件,鼠标拖动由Raphael中的mousedown
、mouseup
和mousemove
组成。
很难区分click
和drag
,因为click
还包含mousedown
和mouseup
,那么在javascript中如何区分鼠标“点击”和鼠标“拖动”?
【问题讨论】:
【参考方案1】:我认为区别在于mousedown
和mouseup
之间有一个mousemove
,在拖动中而不是在单击中。
你可以这样做:
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)
【讨论】:
只要记住在 mousemove 上需要一个最小的 delta X 或 Y 来触发拖动。尝试单击并进行拖动操作会令人沮丧,因为鼠标移动是一个勾号 我认为这在最新的 chrome 中不再有效:32.0.1700.72 无论您是否移动鼠标,Mousemove 都会触发 这个接受的答案代码应该包括在mousedown
和mouseup
的XY 鼠标坐标之间的最小增量条件,而不是听mousemove
事件来设置标志。此外,它将解决@mrjrdnthms 提到的问题
我运行的是 Chrome 56.0.2924.87(64 位),但没有遇到 @mrjrdnthms 所描述的问题。
@AmerllicA 这可能不是错误,而是预期的行为,但是如果您的用例感兴趣,您可以观看 mouseenter 和 mouseleave 事件【参考方案2】:
清洁器 ES2015
let drag = false;
document.addEventListener('mousedown', () => drag = false);
document.addEventListener('mousemove', () => drag = true);
document.addEventListener('mouseup', () => console.log(drag ? 'drag' : 'click'));
正如其他人评论的那样,没有遇到任何错误。
【讨论】:
这会受到带有微小动作的点击的影响。 @AmirKeibi 您可以计算鼠标移动的次数(甚至可以计算两次点击之间的距离,但那将是矫枉过正)【参考方案3】:所有这些解决方案要么在微小的鼠标移动时中断,要么过于复杂。
这是一个使用两个事件侦听器的简单适应性解决方案。 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!
);
【讨论】:
迄今为止最好的答案! 嗨@andreyrd,我可以知道delta
用于此目的吗?这与移动设备中的点击有关吗?
@Haziq 我认为正如人们在***解决方案的 cmets 中提到的那样,delta
用于“尝试单击并进行拖动操作会令人沮丧,因为鼠标移动只有一个刻度”
我用解释更新了答案。基本上如果你的手指小于 6 像素,它仍然会算作一次点击。如果它移动 6 个或更多像素,则将被视为拖动。
不错。请注意,这在 某些 情况下无法正常使用。 一个例子:拖放。由于用户改变主意而将某些东西拖走但又带回来可能会导致不希望的点击。在这种情况下,必须在mousemove
上检查阈值增量,正如这里的一些答案所建议的那样。【参考方案4】:
如果你已经在使用 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
。像其他 cmets 所说的那样,这里可能需要一个额外的范围。
@ChiMo 我一直在使用的是从第一个evt
存储鼠标位置并与第二个evt
的位置进行比较,例如:if (evt.type === 'mouseup' || Math.abs(evt1.pageX - evt2.pageX) < 5 || Math.abs(evt1.pageY - evt2.pageY) < 5) ...
。
我尝试了这个问题的所有其他答案,这是检查.on('mouseup mousemove touchend touchmove')
时唯一有效的答案,除此之外没有设置位置变量。很好的解决方案!
有时当我单击一个元素时,“evt.type”会在 mouseup 上返回“mousemove”。我该如何解决这个问题?【参考方案5】:
这应该很好用。类似于接受的答案(尽管使用 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
中调整坐标检查。
【讨论】:
【参考方案6】:如果您想使用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
运算符将从源中获取最新值(mousedown
和 mousemove
的 merge
)并在内部可观察对象 (mouseup
) 发出时发出。
【讨论】:
我用 observables 编写所有代码,这样我的老板就不能雇用其他人来代替我。【参考方案7】:正如 mrjrdnthms 在他对已接受答案的评论中指出的那样,这不再适用于 Chrome(它总是会触发 mousemove),我已经修改了 Gustavo 的答案(因为我使用的是 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
函数来自这个answer
【讨论】:
这几乎对我有用,但我从[evt.pageX, evt.pageY].equals()
命令中得到了一个错误。我用(evt.pageX === currentPos[0] && evt.pageY===currentPos[1])
替换了它,一切都很好。 :)
equals
代码需要从我的帖子底部的链接中添加
啊,这就解释了。谢谢。
我似乎无法理解其中的逻辑。为什么要在mousemove
上更新currentPos
?这是否意味着您会将一些拖动视为点击?
如果您在移动鼠标的同时"mouseup"
不会触发。【参考方案8】:
就这么简单
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!! ")
)
老实说,您不想添加阈值,允许小幅移动。以上是点击所有桌面界面的正确、正常、感觉。
试试吧。
如果您愿意,可以轻松add an event。
【讨论】:
【参考方案9】:使用带有 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");
);
【讨论】:
【参考方案10】:如果只是为了过滤掉拖拽案例,可以这样:
var moved = false;
$(selector)
.mousedown(function() moved = false;)
.mousemove(function() moved = true;)
.mouseup(function(event)
if (!moved)
// clicked without moving mouse
);
【讨论】:
【参考方案11】:你可以这样做:
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>
【讨论】:
我采纳了您的方法并为任何感兴趣的人创建了React version。【参考方案12】:使用距离阈值的基于类的 vanilla 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 也可以是document为全局):
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);
【讨论】:
【参考方案13】:最近在树形列表中遇到了同样的问题,用户可以单击或拖动它,创建这个小的 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;
在这里你可以看到它在工作:
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;
const pointer = new Pointer();
window.addEventListener('mousedown', (e) => pointer.start(e))
//window.addEventListener('mousemove', (e) => pointer.last(e))
window.addEventListener('mouseup', (e) =>
const operation = pointer.isClick(e)
? "Click"
: "Drag"
console.log(operation)
)
【讨论】:
【参考方案14】:带有 DeltaX 和 DeltaY 的纯 JS
comment 在接受的答案中建议使用此 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);
【讨论】:
【参考方案15】:对于 OSM 地图上的公共操作(单击时放置标记),问题是:1)如何确定鼠标向下->向上的持续时间(您无法想象为每次单击创建一个新标记)和2) 鼠标在 down->up 期间是否移动过(即用户正在拖动地图)。
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);
【讨论】:
【参考方案16】:根据this 的回答,我在我的 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 />
);
)
【讨论】:
【参考方案17】:如果您想检查特定元素的单击或拖动行为,您可以执行此操作而无需听正文。
$(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>
【讨论】:
【参考方案18】:来自@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
);
【讨论】:
【参考方案19】:下面的编码是检测mouseup
和mousedown
的移动。
它适用于大多数情况。这也取决于 关于如何将
mouseevent
视为点击。在 JavaScript 中,检测非常简单。它不关心如何 长按或在 mousedown 和 mouseup 之间移动。
Event.detail
在鼠标移动时不会重置为 1mousedown
和mouseup
。如果你需要区分点击和长按,你需要 检查
event.timeStamp
的差异。
// ==== add the code at the begining of your coding ====
let clickStatus = 0;
(() =>
let screenX, screenY;
document.addEventListener('mousedown', (event) => (screenX, screenY = event), true);
document.addEventListener('mouseup', (event) => (clickStatus = Math.abs(event.screenX - screenX) + Math.abs(event.screenY - screenY) < 3), true);
)();
// ==== add the code at the begining of your coding ====
$("#draggable").click(function(event)
if (clickStatus)
console.log(`click event is valid, click count: $event.detail`)
else
console.log(`click event is invalid`)
)
<!doctype html>
<html lang="en">
<!-- coding example from https://jqueryui.com/draggable/ -->
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>jQuery UI Draggable - Default functionality</title>
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<link rel="stylesheet" href="/resources/demos/style.css">
<style>
#draggable width: 150px; height: 150px; padding: 0.5em;
</style>
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script>
$( function()
$( "#draggable" ).draggable();
);
</script>
</head>
<body>
<div id="draggable" class="ui-widget-content">
<p>Drag me around</p>
</div>
</body>
</html>
【讨论】:
以上是关于如何区分鼠标“点击”和“拖动”的主要内容,如果未能解决你的问题,请参考以下文章