如何区分鼠标“点击”;和“;drag"

我使用jQuery.click来处理Raphael图上的鼠标点击事件,同时,我需要处理鼠标drag事件,鼠标拖动由Raphael中的mousedown, __abc3和mousemove组成。

很难区分clickdrag,因为click还包含mousedown &mouseup,我如何区分鼠标“点击”&鼠标“拖动”然后在Javascript?

229604 次浏览

我认为区别在于在拖拽中mousedownmouseup之间有一个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)

如果你已经在使用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);
});
});

正如mrjrdnthms在他对接受的答案的评论中指出的那样,这不再适用于Chrome(它总是触发鼠标移动),我已经改编了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函数来自于这个回答

这应该能很好地工作。类似于接受的答案(虽然使用jQuery),但只有当新的鼠标位置与mousedown事件上的位置不同时,isDragging标志才会重置。与公认的答案不同,这适用于最新版本的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中调整坐标检查,如果你想增加一点公差(即将微小的移动视为点击,而不是拖动)。

如果只是为了过滤掉拖动的情况,这样做:

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

使用jQuery和5像素x/y暂停来检测拖动:

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

如果你想使用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操作符将从源(mousedownmousemovemerge)中获取最新的值,并在内部可观察对象(mouseup)发出时发出该值。

清洁ES2015

let drag = false;


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

正如其他人评论的那样,没有遇到任何bug。

纯JS与DeltaX和delay

这个DeltaX和delay是由评论在接受的答案中建议的,以避免由于鼠标移动而试图单击并获得拖动操作时的令人沮丧的体验。

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

对于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使用距离阈值的解决方案

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

所有这些解决方案要么在微小的鼠标移动上失效,要么过于复杂。

下面是一个使用两个事件侦听器的简单适应性解决方案。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!
}
});

如果您希望检查特定元素的单击或拖动行为,则无需监听主体即可执行此操作。

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

你可以这样做:

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组件中这样做:

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

其实就是这么简单

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

你真的不想添加阈值允许一个小的运动。以上是正确的,正常的,点击所有桌面界面的感觉。

试试吧。

如果你喜欢,你可以很容易地添加事件

下面的编码是检测mouseupmousedown的移动。

它适用于大多数情况。这也取决于 关于你如何对待mouseevent作为单击。

在JavaScript中,检测非常简单。它不关心如何 在鼠标下拉和鼠标上拉之间按住或移动。 Event.detail将不会重置为1当你的鼠标移动 mousedownmouseup.

如果你需要区分点击和长按,你需要 也检查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>

有同样的问题,最近在一个树列表中,用户可以单击项目或拖动它,使这个小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)
})

很简单,

el = document.getElementById("your_id");
var isDown = false;
el.addEventListener('mousedown', function () {
isDown = true;
});
el.addEventListener('mouseup', function () {
isDown = false;
});
el.addEventListener('mousemove', function () {
if (isDown) {
// your code goes here
}
});