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
Tagged , , , . Bookmark the permalink.

14 Responses to Click and Drag multi-selection rectangle with Javascript

  1. Alexey says:

    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. Thanks for that, I corrected it.

  3. EdJr says:

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

  4. 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!

  5. Ronald says:

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

  6. Thanks Ronald, I edited the code.

  7. Danny says:

    Very good tutorial. Works like a charm, thanks!

  8. HANU says:

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

  9. Yes start the drag with onTouchStart event, then change the x,y values based on the touchMove pointer event and release onTouchEnd event.

  10. Lars Emil Christensen says:

    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!

  11. Lars Emil Christensen says:

    Sorry, link died to the gif – got another here:
    http://gph.is/1Sgb7xe

  12. Nice, thanks for sharing!

  13. bill says:

    Nice Work Michael

  14. Gianluca Ciambellotti says:

    This is really usefull, thanks a lot!!!!

Leave a Reply

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