Capturing client-side mouse events with jQuery and JavaScript
JavaScript library for capturing client-side mouse events.
Introduction
I have seen several client-side libraries that allow you to trap certain events - single, double, triple clicks. However I needed a more robust library that granted configurable mouse-up events as well. This JavaScript library is the result of that.
Instead of writing your own code to capture various mouse events, and deal with the issues of figuring out how to capture a double-click vs. two single-clicks, or even a triple-click, this code will show you how to accomplish this.
Background
I had a requirement to write a camera's PTZ (pan, tilt, zoom) controls and wanted to give the users a wide range of mouse-related events.
Using the code
The code essentially hooks into a div called mouseLayer
. All you have to do is make references to jQuery, some CSS that positions your div somewhere, and the following JavaScript library:
// vlcContainer can be defined from the calling file - this is the ID
// of the VLC player. if not, the default ID is vlc-player
var vlc;
// cascading events can occur - detection of end events such as single, double and triple left clicks
// for example:
// - single left-click without movement does not need to send delta (0, 0)
// - single left-click with movement needs to send a delta(0, 0)
// - double left-click - trigger event, but no need to send delta (0, 0) without movement
// - double left-click with movement (capture X and/or Y); send delta (0, 0)
// send a stopped event if movement with movement detected {t/f}
// if true, stop event sent on button release if movement occurred
// if false, stop event sent on button release
var stopEventWithMovement = {};
stopEventWithMovement["leftSingleClick"] = true;
stopEventWithMovement["leftDoubleClick"] = true;
stopEventWithMovement["leftTripleClick"] = true;
stopEventWithMovement["rightSingleClick"] = true;
stopEventWithMovement["rightDoubleClick"] = true;
stopEventWithMovement["rightTripleClick"] = true;
function getVLC(name) {
if (window.document[name]) {
return window.document[name];
}
if (navigator.appName.indexOf("Microsoft Internet") == -1) {
if (document.embeds && document.embeds[name])
return document.embeds[name];
}
else // if (navigator.appName.indexOf("Microsoft Internet")!=-1)
{
return document.getElementById(name);
}
return null;
}
function registerVideoContainer() {
// you can define the name ID of the player in JS in the source file
if (typeof vlcContainer == 'undefined') {
vlc = getVLC('vlc-player');
} else {
vlc = getVLC(vlcContainer);
}
try {
console.log("VLC plugin version: " + vlc.versionInfo());
// v 2.0.8 Twoflower
} catch (e) {
// do nothing
}
var options = new Array(":aspect-ratio=4:3", "--rtsp-tcpasdfasdf");
var id = vlc.playlist.add("rtsp://10.120.7.193/stream1", "LIVE STREAM", options);
//var id = vlc.playlist.add("http://people.videolan.org/~dionoea/
// vlc-plugin-demo/streams/sw_h264.asf", "LIVE STREAM", options);
vlc.playlist.playItem(id);
}
$(document).ready(function() {
registerVideoContainer();
var zoomFactor = 0;
var relativeStartPosition = function() {
this.X = 0;
this.Y = 0;
};
var relativePosition = function() {
this.X = 0;
this.Y = 0;
};
var deltaPosition = function() {
this.X = 0;
this.Y = 0;
};
var hasClickMovement = false;
var leftSingleClick = false;
var leftDoubleClick = false;
var leftTripleClick = false;
var rightSingleClick = false;
var rightDoubleClick = false;
var rightTripleClick = false;
var parentOffset;
var clickCount = 0;
var longClick = false;
var isMouseDown = false;
var clickCounter = {
increment: function (mouseButton) {
if (clickCounter._timeout != null)
clearTimeout(clickCounter._timeout);
if (clickCounter.mouseButton != mouseButton) {
clickCounter.count = 0;
clickCounter.mouseButton = mouseButton;
}
clickCounter.count++;
clickCounter._timeout = setTimeout(clickCounter.reset, 200);
},
reset: function () {
clearTimeout(clickCounter._timeout);
clickCounter._timeout = null;
if (clickCounter.count > 0) {
// TODO: Raise event.
//console.log("raise event");
//console.log(clickCounter.target);
$(clickCounter.target).trigger("clickCountEvent",
{ count: clickCounter.count, which: clickCounter.mouseButton });
}
clickCounter.count = 0;
clickCounter.mouseButton = null;
},
target: null,
_timeout: null,
mouseButton: null,
count: 0
};
clickCounter.target = $("#mouseLayer").get(0);
console.log("test:" + clickCounter.target);
//
// hook into the following action end events
//
function endCurrentAction() {
deltaPosition.X = 0;
deltaPosition.Y = 0;
if (leftSingleClick & (hasClickMovement && stopEventWithMovement["leftSingleClick"])) {
// finish single-click with movement
clickCounter.count = 0;
$("#deltaDisplay").html("Left Click Stopped: (" +
deltaPosition.X + ", " + deltaPosition.Y + ")");
} else if (leftDoubleClick & (hasClickMovement && stopEventWithMovement["leftDoubleClick"])) {
// finish double-click with movement
$("#deltaDisplay").html("Left Double-Click Stopped: (" +
deltaPosition.X + ", " + deltaPosition.Y + ")");
} else if (leftTripleClick & (hasClickMovement && stopEventWithMovement["leftTripleClick"])) {
// finish triple-click with movement
clickCounter.count = 0;
$("#deltaDisplay").html("Left Triple-Click Stopped: (" +
deltaPosition.X + ", " + deltaPosition.Y + ")");
}
if (leftSingleClick & !stopEventWithMovement["leftSingleClick"]) {
// finish double-click with no movement
clickCounter.count = 0;
$("#deltaDisplay").html("Left Click Stopped: (" +
deltaPosition.X + ", " + deltaPosition.Y + ")");
} else if (leftDoubleClick & !stopEventWithMovement["leftDoubleClick"]) {
// finish double-click with no movement
clickCounter.count = 0;
$("#deltaDisplay").html("Left Double-Click Stopped: (" +
deltaPosition.X + ", " + deltaPosition.Y + ")");
} else if (leftTripleClick & !stopEventWithMovement["leftTripleClick"]) {
// finish triple-click with no movement
clickCounter.count = 0;
$("#deltaDisplay").html("Left Triple-Click Stopped: (" +
deltaPosition.X + ", " + deltaPosition.Y + ")");
}
if (rightTripleClick & (hasClickMovement && stopEventWithMovement["rightTripleCLick"])) {
$("#deltaDisplay").html("Right Triple-Click Stopped: (" +
deltaPosition.X + ", " + deltaPosition.Y + ")");
} else if (rightDoubleClick & (hasClickMovement && stopEventWithMovement["rightDoubleClick"])) {
$("#deltaDisplay").html("Right Double-Click Stopped: (" +
deltaPosition.X + ", " + deltaPosition.Y + ")");
} else if (rightSingleClick & (hasClickMovement && stopEventWithMovement["rightSingleClick"])) {
$("#deltaDisplay").html("Right Click Stopped: (" +
deltaPosition.X + ", " + deltaPosition.Y + ")");
}
leftSingleClick = false;
leftDoubleClick = false;
leftTripleClick = false;
rightSingleClick = false;
rightDoubleClick = false;
}
// disable the browser's context menu for right-clickability
$(document).bind("contextmenu", function(e) {
return false;
});
$("#mouseLayer").mousemove(function (e) {
$('body').css('cursor', 'crosshair');
parentOffset = $(this).parent().offset();
//offset -> method allows you to retrieve the current
// position of an element 'relative' to the document
relativeStartPosition.X = (e.pageX - parentOffset.left);
relativeStartPosition.Y = (e.pageY - parentOffset.top);
deltaPosition.X = relativeStartPosition.X - relativePosition.X;
deltaPosition.Y = relativePosition.Y - relativeStartPosition.Y;
if (deltaPosition.X != 0 | deltaPosition.Y != 0) {
//
// add hooks here on mouse-move for each button click type, capturing the delta positions
//
if (leftSingleClick) {
hasClickMovement = true;
$('body').css('cursor', 'move');
$("#deltaDisplay").html("Delta: (" +
deltaPosition.X + ", " + deltaPosition.Y + ")");
$("#movement").html(" !!!").css("display", "inline").fadeOut("slow");
} else if (leftDoubleClick) {
hasClickMovement = true;
$("#deltaDisplay").html("Delta: (" + deltaPosition.X + ", " + deltaPosition.Y + ")");
$("#movement").html(" !!!").css("display", "inline").fadeOut("slow");
} else if (leftTripleClick) {
hasClickMovement = true;
$("#deltaDisplay").html("Delta: (" + deltaPosition.X + ", " + deltaPosition.Y + ")");
$("#movement").html(" !!!").css("display", "inline").fadeOut("slow");
} else if (rightSingleClick) {
hasClickMovement = true;
$("#deltaDisplay").html("Delta: (" + deltaPosition.X + ", " + deltaPosition.Y + ")");
$("#movement").html(" !!!").css("display", "inline").fadeOut("slow");
} else if (rightDoubleClick) {
hasClickMovement = true;
$("#deltaDisplay").html("Delta: (" +
deltaPosition.X + ", " + deltaPosition.Y + ")");
$("#movement").html(" !!!").css("display", "inline").fadeOut("slow");
} else if (rightTripleClick) {
hasClickMovement = true;
$("#deltaDisplay").html("Delta: (" +
deltaPosition.X + ", " + deltaPosition.Y + ")");
$("#movement").html(" !!!").css(
"display", "inline").fadeOut("slow");
} else {
relativePosition.X = 0;
relativePosition.Y = 0;
//relativeStartPosition.X = 0;
//relativeStartPosition.Y = 0;
}
}
$("#position").html("<p><strong>X-Position: </strong>" +
relativeStartPosition.X + " | <strong>Y-Position: </strong>" +
relativeStartPosition.Y + "</p>");
})
.bind("clickCountEvent", function (e, data) {
//console.log("click count: " + data.count);
switch (data.which) {
case 1:
// left
// now if mouse is moved, return new relative positions
if (data.count == 1) { // left single-click
if (!leftSingleClick) {
relativePosition.X = relativeStartPosition.X;
relativePosition.Y = relativeStartPosition.Y;
}
if (isMouseDown) {
leftSingleClick = true;
leftDoubleClick = false;
leftTripleClick = false;
$("#clicked").html("left click");
}
} else if (data.count == 2) { // left double-click
if (!leftDoubleClick) {
relativePosition.X = relativeStartPosition.X;
relativePosition.Y = relativeStartPosition.Y;
}
if (isMouseDown) {
leftSingleClick = false;
leftDoubleClick = true;
leftTripleClick = false;
$("#clicked").html("double-left click");
$("#zoomFactor").html("Zoom In (click)").css(
"display", "inline").fadeOut("slow");
}
} else if (data.count == 3) { // left triple-click
if (!leftTripleClick) {
relativePosition.X = relativeStartPosition.X;
relativePosition.Y = relativeStartPosition.Y;
}
if (isMouseDown) {
leftSingleClick = false;
leftDoubleClick = false;
leftTripleClick = true;
$("#clicked").html("triple-left click");
}
}
break;
case 2:
// middle
$("#clicked").html("middle click");
break;
case 3:
// right
if (data.count == 1) {
// right single click
if (!rightSingleClick) {
relativePosition.X = relativeStartPosition.X;
relativePosition.Y = relativeStartPosition.Y;
}
if (isMouseDown) {
rightSingleClick = true;
rightDoubleClick = false;
rightTripleClick = false;
$("#clicked").html("right click");
}
} else if (data.count == 2) {
// right double click
if (!rightDoubleClick) {
relativePosition.X = relativeStartPosition.X;
relativePosition.Y = relativeStartPosition.Y;
}
if (isMouseDown) {
rightSingleClick = false;
rightDoubleClick = true;
rightTripleClick = false;
$("#clicked").html("double-right click");
$("#zoomFactor").html("Zoom Out (click)").css(
"display", "inline").fadeOut("slow");
}
} else if (data.count == 3) {
// right triple click
$("#clicked").html("triple-right click");
if (!rightDoubleClick) {
relativePosition.X = relativeStartPosition.X;
relativePosition.Y = relativeStartPosition.Y;
}
if (isMouseDown) {
rightSingleClick = false;
rightDoubleClick = false;
rightDoubleClick = true;
$("#clicked").html("triple-left click");
}
}
break;
default:
// error
$("#clicked").html("unsupported mouse: " + e.which);
}
})
.mousedown(function (e) {
isMouseDown = true;
clickCounter.increment(e.which);
})
.mouseout(function () {
clickCounter.count = 0;
isMouseDown = false;
$("#position").html("<p><strong>X-Position: </strong>" +
relativeStartPosition.X + " | <strong>Y-Position: </strong>" +
relativeStartPosition.Y + "</p>");
$('body').css('cursor', 'default');
if ((deltaPosition.X != 0 && deltaPosition.Y != 0) |
(!isNaN(deltaPosition.X) && !isNaN(deltaPosition.Y))) {
//console.log(deltaPosition.X + ", " + deltaPosition.Y);
endCurrentAction();
}
})
.mouseup(function (event) {
isMouseDown = false;
$("#clicked").html("");
$("#deltaDisplay").html("");
$('body').css('cursor', 'crosshair');
endCurrentAction();
hasClickMovement = false;
//console.log("Mouse up dPos: " + (deltaPosition.X + ", " + deltaPosition.Y));
});
$('#mouseLayer').bind('mousewheel', function (event, delta) {
if (event.originalEvent.wheelDelta > 0) {
$("#zoomFactor").html("Zoom In").css(
"display", "inline").fadeOut("slow");
} else {
$("#zoomFactor").html("Zoom Out").css(
"display", "inline").fadeOut("slow");
}
return false;
});
});
Explaining the Code
In #mouseLayer
, we
.bind()
the clickCountEvent
function which responds
to the appropriate data.count
through the switch()
statement.
The number of clicks is managed by the
clickCounter
where the output of number of clicks is determined by the delay between clicks, in this case, 200ms:
clickCounter._timeout = setTimeout(clickCounter.reset, 200);
In each switch section, we begin tracking the delta X/Y during the period of time that the button is pressed. It is through these values (think of a new vector being created at time of clicking) that you can set an acceleration factor for whatever you are controlling.
You may also need to send a "stop" event for your device. This is achieved through the release of the button click.
You can choose whether or not to raise a stop event (as you may not need to if the mouse has not been moved), in the stopEventWithMovement[whichButtonClickType]={bool}
array defined at the file header.
Styling
Here's how I set the CSS for the activation DIV:
/* positioning is relative to the container */
#mouseLayer{
position: absolute;
top:10px;
left:10px;
height: calc(90% - 20px);
width: calc(90% - 10px);
/*height: auto;*/
/*min-height:480px;*/
margin:0px auto;
overflow-y: auto;
z-index:1000;
text-align:left;
padding:15px;
border:1px dashed #333;
/*background-color:#0000ee;*/
background-color:rgba(255,0,0,0.05);
padding-top: 30px;
overflow: hidden;
}
Points of Interest
To view diagnostic code of how the events fire upon activation, see http://www.whatsinyourlunch.com/capturing-client-side-mouse-events-with-javascript-jquery/. Here I go into further detail of how certain
conditionally mouseUp
events are tracked and parameters are set.
You can actually set events to N number of clicks by simply adding new triggerable values within the switch statement. Thus, you could trigger an event for 10 left-clicks, 30 right-clicks, etc.
History
No major history.