Click here to Skip to main content
15,867,453 members
Articles / Programming Languages / Javascript

Capturing client-side mouse events with jQuery and JavaScript

Rate me:
Please Sign up or sign in to vote.
4.60/5 (3 votes)
18 Sep 2013CPOL2 min read 19.7K   14   1
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:  

C++
// 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: 

C#
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: 

CSS
/* 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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Systems Engineer
United States United States
I am a technical architect/senior software engineer, technical trainer, entrepreneur.

I have worked in several sectors from healthcare, to entertainment and global intelligent traffic systems, using .Net, SQL, NoSQL and some of the latest technologies to deliver quality software systems for clients.

Past tech flavors include C#, WCF, SOA, MVC, MVVM, Silverlight, Assembler, Pascal, VB, Java/J2EE/EJB/JDBC, Perl, NLTK, TSQL, NoSQL, KendoUI, NodeJS, SignalR, Backbone JS, Angular JS, Latest .Net technologies, Amazon AWS...

Comments and Discussions

 
Questionwhich vlc web plugin did you use in this example? Pin
杨阳20-Aug-14 20:15
杨阳20-Aug-14 20:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.