Search code examples
javascriptjquerypositioningcss-position

How can I make absolute positioned elements not overlapping each other?


Here is what I want to do: I'm using gridster in order to create a form creation IDE. The user drags elements in the grid, and can move them around.

Everything works great inside the editor, but when I try to use those forms but I'm having issues when the elements inside the grid positions become bigger than the grid block that contains them, making one element overlapping another.

What I'm looking for is an automated solution that when given a bunch of absolute positioned elements, it moves them up or down so they don't overlap. Does such thing exist?


Solution

  • I made a workaround myself

    I wrote this code, and put it inside an interval that loops all the time, making the elements that belong to the same column not overlapping.

    Take note that the .gs_w is the class of the gridster grid cell which contains the .form_row which in turn contains the input element itself and shrinks to fit it.

    Finally this won't work if dragging is enabled, which in my case is ok because only the form editor I built utilises dragging. Not the forms themselves.

    setInterval(collision_checks);
    
    /**
     * Handles vertical collisions of gridster elements
     */
    function collision_checks()
    {
        var elements = $('.gs_w');
        if(!$(elements).length)
        {
            return;
        }
    
        var columns = {};
        // Group the elements in columns
        $(elements).each(function(){
            //If an element takes more than one columns, add it in all of them
            //so collision gets handled properly
            for(var i = 0; i < $(this).data('sizex'); i++)
            {
                columns[parseInt($(this).data('col'), 10) + i] = columns[parseInt($(this).data('col'), 10) + i] || [];
                columns[parseInt($(this).data('col'), 10) + i].push(this);
            }
    
        });
    
        // For each column, handle if it's elements collides with the element below it
        for(var i in columns)
        {
            if(!columns.hasOwnProperty(i)){continue;}
            var column = columns[i];
            for(var j in column)
            {
                if(!column.hasOwnProperty(j)){continue;}
                var element = column[j];
                var element_below = column[parseInt(j,10) + 1];
    
                if(!$(element).is(':visible'))
                {
                    continue;
                }
    
                if(element == element_below)
                {
                    continue;
                }
    
                var depth = penetration_depth(element, element_below);
                if(depth)
                {
                    var offset = $(element_below).position();
                    $(element_below).css('top', offset.top + depth);
                }
            }
        }
    }
    
    
    /**
     * if the top element overlaps the bottom one, this function returns their penetration depth
     */
    function penetration_depth(el_top, el_bottom)
    {
        if(!el_bottom){return 0;}
        if($(el_top).find('.form_row').offset().top + $(el_top).find('.form_row').outerHeight(true) > $(el_bottom).find('.form_row').offset().top)
        {
            return $(el_top).find('.form_row').offset().top + $(el_top).find('.form_row').outerHeight(true) - $(el_bottom).find('.form_row').offset().top;
        }
        else
        {
            return 0;
        }
    }
    

    Any suggestions or improvements are welcome!