1

I have an bunch of draggable elements, that are absolutely positioned on an "artboard". Users can edit these elements, and also drag them.

Most important thing that user can lasso-select or shift-select many of elements, that applies to element class ".selected", so when dragging one, other selected elements should move too.

Currently this works by modifying top and left position of other elements, but this exposes a snapping problem.

Snapping only works for currently draggable element, but it should work for selected elements too.

Behavior that I want is best described in mockups.

Initial state of artboard

Current behavior

Desired behavior

I tried several different approaches, but with no luck.

Firstly, I tried to calculate selected position of bounding box of selected elements, and in drag event modify ui.position to snap to correct sides with bounding box of all selected elements. That was obviously wrong, so modifying ui position -> modifying helper element, which results in some weird jumping of elements.

After that, I tried to lock position of other elements, when bounding box of selected elements about to snap, this works for other elements, but that one that is dragged keeps moving, and i don't seem a way to stop this behavior.

I tried to modify $.ui.plugin.add('draggable', 'snap', function () {}) part of widget, so it will use different bounding box when calculating snapping, but it results in other weird behavior: when bounding box of selected elements is about to snap, currently draggable element jump (and other elements) jumps to snappable elements.

I tried also to wrap selected elements into div, with helper function of draggable widget, but I can't use this approach, as elements are absolutely positioned, so if I put them inside other absolutely positioned div, they can jump like 1000px in the left for example, so I'll need to somehow recalculate positions, and then when dragging stops - keep correct previous positions.

I can't find how to make this solution work, so I dropped it.

Edit: make small playground to showcase problem and test things https://codepen.io/newfox79/pen/eYjzmGG

mouetttxy
  • 26
  • 4
  • What about [this kind of overlapping](https://i.imgur.com/N6Y0xUh.png)? Would that be acceptable behaviour? – FiddlingAway Dec 31 '22 at 21:41
  • @FiddlingAway For sure, this overlapping can be possible, I don't mention this in my question, but we have a snapTolerance setting about 6-10 (different for some elements), so if we going out of this threshold, for example -7px relative to right border of snappable element, we should allow dragging further – mouetttxy Dec 31 '22 at 23:45
  • Are you checking for collisions and overlaps only within the same parent? That is to say - in your visual example, the white background is the container, and you're only checking for overlaps and collisions within that container, between sibling elements? Is it possible to drag your selection out of its container and check for collisions with things outside of it, like [in this example](https://i.imgur.com/WVidTHr.png)? The example above the thick red line shows draggability outside of the red rectangle (container). The one below the red line shows the opposite. – FiddlingAway Jan 01 '23 at 10:33
  • @FiddlingAway In real app, there's thing like "artboard" (basically white rectangle), and stretchable "canvas" that about 2000px in each side from artboard, elements can be in any position in that range, and should be snappable to each other In my playground there's simplified version of how it works, but in real app when we start to drag, elements recieve "selected class", and selector for snappable looks like ".elem:not(.selected)", so each item that is not currently draggable becomes snappable – mouetttxy Jan 01 '23 at 10:53

1 Answers1

0

You might be able to achieve what you need by using getBoundingClientRect().

But, instead of checking whether just one div overlaps or collides with another, you would have a checkup of overlapping of any of the elements which are selected against all of the other elements within your container / artboard.

function isOverlapping(snapHolder, dragHolder) {
    // let's loop through all of the draggables...
    for(var i = 0; i < dragHolder.length; i++) {
        // ... and check for collisions / overlaps with
        // other elements within the artboard
        for(var j = 0; j < snapHolder.length; j++) {
            var dragged = dragHolder[i].getBoundingClientRect();
            var snapped = snapHolder[j].getBoundingClientRect();

            // is there an overlap?
            if(
                dragged.right > snapped.left && 
                dragged.left < snapped.right && 
                dragged.bottom > snapped.top && 
                dragged.top < snapped.bottom
            ) {
                // raise the flag
                return true;
            }
        }
    }

    // all clear, nothing's overlapping, continue
    return false;
}

snapHolder is a collection of div elements within the artboard, which have the class snappable (that is to say, they're not selected for dragging).

Similarly, dragHolder is a collection of div elements in the artboard, which are selected for draggin.

If I've understood your current code correctly, you would call this function within your processDrag, and within your startDrag. The function would check for any overlaps between the two collections.

Keep in mind that this might not be the most optimal solution - if there's a large number of elements within your artboard, the collision checking might be laggy. In case of x total elements inside the artboard, and y of them being selected for dragging, you're looking at:

(x - y) * y

checkups per cycle (as long as the dragging lasts). In the event of selecting half of the elements, this would become y2 checkups per cycle.

There are other solutions, relying on external jQuery libraries, as suggested in the following SO answers. Be mindful of the date, though:

FiddlingAway
  • 1,598
  • 3
  • 14
  • 30
  • Detecting overlap not the problem I currently facing. For now I re-use [JQuery draggable snippet](https://github.com/jquery/jquery-ui/blob/main/ui/widgets/draggable.js#L1084) that checks collision between snapped elements and current draggable. But problem is i feel lack of control when trying to reposition elements for custom snapping. In my app there's no "incorrect" way of drop element, if you move elements to any position - is totally valid action. But collision keyword is one that I don't think about, so will research further, also jquery-ui-draggable-collision looks promising – mouetttxy Jan 01 '23 at 12:01