How to distinguish mouse "click" and "drag"

JavascriptDom Events

Javascript Problem Overview


I use jQuery.click to handle the mouse click event on Raphael graph, meanwhile, I need to handle mouse drag event, mouse drag consists of mousedown, mouseupand mousemove in Raphael.

It is difficult to distinguish click and drag because click also contain mousedown & mouseup, How can I distinguish mouse "click" & mouse "drag" then in Javascript?

Javascript Solutions


Solution 1 - Javascript

I think the difference is that there is a mousemove between mousedown and mouseup in a drag, but not in a click.

You can do something like this:

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)

Solution 2 - Javascript

Cleaner ES2015

let drag = false;

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

Didn't experience any bugs, as others comment.

Solution 3 - Javascript

All these solutions either break on tiny mouse movements, or are overcomplicated.

Here is a simple adaptable solution using two event listeners. Delta is the distance in pixels that you must move horizontally or vertically between the up and down events for the code to classify it as a drag rather than a click. This is because sometimes you will move the mouse or your finger a few pixels before lifting it.

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

Solution 4 - Javascript

In case you are already using 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);
  });
});

Solution 5 - Javascript

This should work well. Similar to the accepted answer (though using jQuery), but the isDragging flag is only reset if the new mouse position differs from that on mousedown event. Unlike the accepted answer, that works on recent versions of Chrome, where mousemove is fired regardless of whether mouse was moved or not.

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

You may also adjust the coordinate check in mousemove if you want to add a little bit of tolerance (i.e. treat tiny movements as clicks, not drags).

Solution 6 - Javascript

If you feel like using 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/[email protected]/dist/global/Rx.js"></script>

This is a direct clone of what @wong2 did in his answer, but converted to RxJs.

Also interesting use of sample. The sample operator will take the latest value from the source (the merge of mousedown and mousemove) and emit it when the inner observable (mouseup) emits.

Solution 7 - Javascript

As mrjrdnthms points out in his comment on the accepted answer, this no longer works on Chrome (it always fires the mousemove), I've adapted Gustavo's answer (since I'm using jQuery) to address the Chrome behavior.

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

  });

});

The Array.prototype.equals function comes from this answer

Solution 8 - Javascript

It's really this simple

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

You honestly do not want to add a threshold allowing a small movement. The above is the correct, normal, feel of clicking on all desktop interfaces.

Just try it.

You can easily add an event if you like.

Solution 9 - Javascript

Using jQuery with a 5 pixel x/y theshold to detect the drag:

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

Solution 10 - Javascript

You could do this:

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>

Solution 11 - Javascript

If just to filter out the drag case, do it like this:

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

Solution 12 - Javascript

Another solution for class based vanilla JS using a distance threshold

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

Add inside the class (SOMESLIDER_ELEMENT can also be document to be global):

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

Solution 13 - Javascript

Had the same problem recently with a tree list where the user can either click on the item or drag it, made this small Pointer class and put it in my 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;
    }
  }
}

Here you can see it at work:

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

Solution 14 - Javascript

Pure JS with DeltaX and DeltaY

This DeltaX and DeltaY as suggested by a comment in the accepted answer to avoid the frustrating experience when trying to click and get a drag operation instead due to a one tick mousemove.

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

Solution 15 - Javascript

For a public action on an OSM map (position a marker on click) the question was: 1) how to determine the duration of mouse down->up (you can't imagine creating a new marker for each click) and 2) did the mouse move during down->up (i.e user is dragging the map).

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

}

Solution 16 - Javascript

Based on this answer, I did this in my React component:

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

Solution 17 - Javascript

If you want check the click or drag behavior of a specific element you can do this without having to listen to the body.

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

Solution 18 - Javascript

from @Przemek 's answer,

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

Solution 19 - Javascript

The following coding is to detect the movement of mouseup and mousedown.

> It shall work for most of the cases. It also depends > on how you treat mouseevent as Click. > > In JavaScript, the detection is very simple. It does not concern how > long you press or movement between the mousedown and mouseup. > Event.detail would not reset to 1 when your mouse is moved between > the mousedown and mouseup. > > If you need to differentiate the click and long press, you need to > check the difference in event.timeStamp too.

// ==== 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>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionLeemView Question on Stackoverflow
Solution 1 - Javascriptwong2View Answer on Stackoverflow
Solution 2 - JavascriptPrzemekView Answer on Stackoverflow
Solution 3 - JavascriptandreyrdView Answer on Stackoverflow
Solution 4 - JavascriptGustavo RodriguesView Answer on Stackoverflow
Solution 5 - Javascriptnirvana-msuView Answer on Stackoverflow
Solution 6 - JavascriptDorusView Answer on Stackoverflow
Solution 7 - JavascriptFrancisco AquinoView Answer on Stackoverflow
Solution 8 - JavascriptFattieView Answer on Stackoverflow
Solution 9 - JavascriptsilverwindView Answer on Stackoverflow
Solution 10 - JavascriptZayanView Answer on Stackoverflow
Solution 11 - JavascriptjqgsninimoView Answer on Stackoverflow
Solution 12 - JavascriptTim RasimView Answer on Stackoverflow
Solution 13 - JavascriptFennecView Answer on Stackoverflow
Solution 14 - JavascriptSyed Waqas BukharyView Answer on Stackoverflow
Solution 15 - JavascriptWoldenView Answer on Stackoverflow
Solution 16 - JavascriptMike KView Answer on Stackoverflow
Solution 17 - JavascriptLasithdsView Answer on Stackoverflow
Solution 18 - JavascriptJehong AhnView Answer on Stackoverflow
Solution 19 - JavascriptChester FungView Answer on Stackoverflow