Click and Drag multi-selection rectangle with Javascript

This is a nice solution for a problem occured when making strategy games or wireframe applications. Or generally when we want to select multiple items and have them in a visible group.
It is a rather simple solution once the math make sense.

Here’s a nice example I made on codepen. Simply click and drag to form a rectangle around one or both of the objects.

See the Pen Multiselect drag by Michael Dobekidis (@netgfx) on CodePen.

So let’s see what is involved in this example:

Markup

<div id="grid">
  <div class="ghost-select"><span></span></div>
</div>

The grid is where we host our rectangle, and where we bind our mouse-events.

Some css is also needed for setting position, z-index, etc

#big-ghost{
  background-color:rgba(239, 28, 190, 0.6);
  border:1px solid #aaf81a;
  position:absolute;
}

.ghost-active {
  display: block !important;
}

.ghost-select > span {
  background-color: rgba(239, 28, 190, 0.6);
  border: 1px solid #b20e8c;
  width: 100%;
  height: 100%;
  float: left;
}

#grid {
  width: 100%;
  height: 100%;
  position: absolute;
}

.ghost-select {
  display: none;
  z-index: 9000;
  position: absolute !important;
  cursor: default !important;
}

Basically we give the ghost-select (our multi-select rectangle) the highest z-index and position absolute.


The script

First let us bind some events and create the rectangle while dragging:

$("#grid").mousedown(function (e) {
       
        $("#big-ghost").remove();
        $(".ghost-select").addClass("ghost-active");
        $(".ghost-select").css({
            'left': e.pageX,
            'top': e.pageY
        });

        initialW = e.pageX;
        initialH = e.pageY;

        $(document).bind("mouseup", selectElements);
        $(document).bind("mousemove", openSelector);

    });

function openSelector(e) {
    var w = Math.abs(initialW - e.pageX);
    var h = Math.abs(initialH - e.pageY);

    $(".ghost-select").css({
        'width': w,
        'height': h
    });
    if (e.pageX <= initialW && e.pageY >= initialH) {
        $(".ghost-select").css({
            'left': e.pageX
        });
    } else if (e.pageY <= initialH && e.pageX >= initialW) {
        $(".ghost-select").css({
            'top': e.pageY
        });
    } else if (e.pageY < initialH && e.pageX < initialW) {
        $(".ghost-select").css({
            'left': e.pageX,
            "top": e.pageY
        });
    }
}

With this basic script and math we are able to create a rectangle by placing one initial point as the top-left corner (if dragging to the right) and top-right (if dragging to the left) and then simply expand following the mouse-cursor.


I release you

Now let’s see what can happen when we realease the mouse and selecting the items.

function selectElements(e) {
    $("#score>span").text('0');
    $(document).unbind("mousemove", openSelector);
    $(document).unbind("mouseup", selectElements);
    var maxX = 0;
    var minX = 5000;
    var maxY = 0;
    var minY = 5000;
    var totalElements = 0;
    var elementArr = new Array();
    $(".elements").each(function () {
        var aElem = $(".ghost-select");
        var bElem = $(this);
        var result = doObjectsCollide(aElem, bElem);

        console.log(result);
        if (result == true) {
          $("#score>span").text( Number($("#score>span").text())+1 );
          var aElemPos = bElem.offset();
                var bElemPos = bElem.offset();
                var aW = bElem.width();
                var aH = bElem.height();
                var bW = bElem.width();
                var bH = bElem.height();

                var coords = checkMaxMinPos(aElemPos, bElemPos, aW, aH, bW, bH, maxX, minX, maxY, minY);
                maxX = coords.maxX;
                minX = coords.minX;
                maxY = coords.maxY;
                minY = coords.minY;
                var parent = bElem.parent();

                //console.log(aElem, bElem,maxX, minX, maxY,minY);
                if (bElem.css("left") === "auto" && bElem.css("top") === "auto") {
                    bElem.css({
                        'left': parent.css('left'),
                        'top': parent.css('top')
                    });
                }
          $("body").append("<div id='big-ghost' class='big-ghost' x='" + Number(minX - 20) + "' y='" + Number(minY - 10) + "'></div>");

            $("#big-ghost").css({
                'width': maxX + 40 - minX,
                'height': maxY + 20 - minY,
                'top': minY - 10,
                'left': minX - 20
            });
         
         
        }
    });
   
    $(".ghost-select").removeClass("ghost-active");
    $(".ghost-select").width(0).height(0);

    ////////////////////////////////////////////////

}

This might seem like fairly complicated function but all it really does is to check if the elements with class elements overlap with our multi-select rectangle. Then if some do, we check their position to determine our bounding box, and then go on and create a rectangle div with those dimensions.

For these functions you can check these two Gists:
Collision detection
Check max-min position

Post comment if you thought this was cool or if you have questions.

Enjoy!

Facebooktwittergoogle_pluspinterestlinkedin
linkedin

14 thoughts on “Click and Drag multi-selection rectangle with Javascript

  1. In openSelector function you got an error in last case:
    else if (e.pageY < initialH && e.pageY < initialW) {
    $(".ghost-select").css({
    'left': e.pageX,
    "top": e.pageY
    });
    }
    There must be e.pageY < initialH && e.pageX < initialW in condition.

  2. Very good tutorial!
    However, I think that all that checking is overkill, and even flawed. If you drag the mouse back and forth (either horizontally or vertically), you’ll see that the position of the rectangle changes drastically, when what you really want is that it remains stable. I found a much easier way to do it, and the problem disappears. I haven’t tested it a lot, so I don’t know if it’s buggy.

    Anywy, you just need to replace all those ifs with the following:

    $(".ghost-select").css({
          'left': Math.min(e.pageX, initialW),
          'top': Math.min(e.pageY, initialH)
        });
  3. I agree that your solution is much cleaner and certainly short, however this code as every snippet on this site is meant to educate the readers to understand what they are reading as a tutorial if you wish.

    I would guess that the Math.min function does something similar to what I did just behind the scenes, although it is more difficult to understand.

    Thank you for your input nevertheless!

  4. Thanks for this! I notice you have a CSS rule for .ghost-select > div, but that element doesn’t seem to exist.

  5. How can we achieve this for touch enable browsers ?
    Same functionality for touchmove event ?

  6. Hi Michael, – thank you for a really brilliant tutorial.

    I had this bookmarked for a long time, and finally found the time to play around with it.

    After playing, I implemented somewhat similar functionality in my own project and it works amazingly.

    Got a small gif demonstrating it here.
    http://g.recordit.co/srmR52AwRz.gif

    Thanks again!

Leave a Reply

Your email address will not be published. Required fields are marked *