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:
var vlc;
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 {
return document.getElementById(name);
}
return null;
}
function registerVideoContainer() {
if (typeof vlcContainer == 'undefined') {
vlc = getVLC('vlc-player');
} else {
vlc = getVLC(vlcContainer);
}
try {
console.log("VLC plugin version: " + vlc.versionInfo());
} catch (e) {
}
var options = new Array(":aspect-ratio=4:3", "--rtsp-tcpasdfasdf");
var id = vlc.playlist.add("rtsp://10.120.7.193/stream1", "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) {
$(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);
function endCurrentAction() {
deltaPosition.X = 0;
deltaPosition.Y = 0;
if (leftSingleClick & (hasClickMovement && stopEventWithMovement["leftSingleClick"])) {
clickCounter.count = 0;
$("#deltaDisplay").html("Left Click Stopped: (" +
deltaPosition.X + ", " + deltaPosition.Y + ")");
} else if (leftDoubleClick & (hasClickMovement && stopEventWithMovement["leftDoubleClick"])) {
$("#deltaDisplay").html("Left Double-Click Stopped: (" +
deltaPosition.X + ", " + deltaPosition.Y + ")");
} else if (leftTripleClick & (hasClickMovement && stopEventWithMovement["leftTripleClick"])) {
clickCounter.count = 0;
$("#deltaDisplay").html("Left Triple-Click Stopped: (" +
deltaPosition.X + ", " + deltaPosition.Y + ")");
}
if (leftSingleClick & !stopEventWithMovement["leftSingleClick"]) {
clickCounter.count = 0;
$("#deltaDisplay").html("Left Click Stopped: (" +
deltaPosition.X + ", " + deltaPosition.Y + ")");
} else if (leftDoubleClick & !stopEventWithMovement["leftDoubleClick"]) {
clickCounter.count = 0;
$("#deltaDisplay").html("Left Double-Click Stopped: (" +
deltaPosition.X + ", " + deltaPosition.Y + ")");
} else if (leftTripleClick & !stopEventWithMovement["leftTripleClick"]) {
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;
}
$(document).bind("contextmenu", function(e) {
return false;
});
$("#mouseLayer").mousemove(function (e) {
$('body').css('cursor', 'crosshair');
parentOffset = $(this).parent().offset();
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) {
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;
}
}
$("#position").html("<p><strong>X-Position: </strong>" +
relativeStartPosition.X + " | <strong>Y-Position: </strong>" +
relativeStartPosition.Y + "</p>");
})
.bind("clickCountEvent", function (e, data) {
switch (data.which) {
case 1:
if (data.count == 1) { 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) { 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) { 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:
$("#clicked").html("middle click");
break;
case 3:
if (data.count == 1) {
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) {
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) {
$("#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:
$("#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))) {
endCurrentAction();
}
})
.mouseup(function (event) {
isMouseDown = false;
$("#clicked").html("");
$("#deltaDisplay").html("");
$('body').css('cursor', 'crosshair');
endCurrentAction();
hasClickMovement = false;
});
$('#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:
#mouseLayer{
position: absolute;
top:10px;
left:10px;
height: calc(90% - 20px);
width: calc(90% - 10px);
margin:0px auto;
overflow-y: auto;
z-index:1000;
text-align:left;
padding:15px;
border:1px dashed #333;
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.