An efficient way to recalculate / recalculate a grid of absolutely positioned elements

Problem Statement

I have a grid, the corners of the elements may or may not be aligned, but all the edges touch so that there are no gaps. There are also no matches. For example, it is possible to have something like this:

+----+-------+
|    |   B   |
|    +---+---+
| A  | C |   |
|    |---| D |
|    | E |   |
+----+---+---+

      

The grid is created through absolutely positioned elements. (I understand it's easier to create a grid like this through a tree where the parent node is a container that forms a rectangle with the sibling element (s), but I think this might limit the ways I could be able to resize the elements - I'll explain later).

I want to be able to resize one element and have adjacent elements, resize them so that they snap to the new sizes of the elements without leaving any gaps. For example, let's say we are resizing an element C

:

  • If I resize the left edge of C towards A, I want A to shrink horizontally. As A contracts, B and E must expand in the direction of A to fill this void.
  • If I change the bottom edge of C downward, E should shrink, no other elements should be affected.
  • If I resize the right edge of C to D, D should shrink, E and C should grow into that void.
  • If I resize the top edge of C to B, B should shrink vertically and D should expand with C.

Why tree structure doesn't work

Now, as mentioned earlier, I understand that nesting these elements inside container elements (tree structure) will make handling the above case much easier. The reason why I think the tree structure won't work for me (besides the fact that I already have too much code based on absolute positions) is because I don't want the next size to be based on the base tree structure that is at the bottom:

+---+---+---+
|   |   |   |
+---+---+---+
|   |   |   |
+---+---+---+
|   |   |   |
+---+---+---+

      

With a tree, this example will not work, as resizing the middle tile will resize elements that have the same parent / container, even if they don't need to be resized.

Current thoughts / work

I am trying to figure out how to calculate which additional elements should be effectively changed for my absolute elements. I am thinking about something along the following lines:

  • After resizing, increasing the element in a given direction, take the corresponding edge and document.elementsFromPoint()

    search along that edge in a binary pattern from one corner to another until the element returned for the min point is the same as for the maximum point for each sample point ( if they don't match, draw a new point in the middle and continue recursively). This stencil will contain all the elements that the element has invaded as a result of the resizing (so they need to be shrunk using the opposite edge)
  • After resizing that shrinks the element, do the same kind of binary traversal along the original edge (before resizing), but a couple of pixels in the opposite direction from the size (this should hit elements that need to grow to fill the gap)
  • For the main element it will be either one or the other bullet higher (shrinking or growing), but the next step now finds "side effects", if the edge of the neighboring element goes beyond the edge of the original element, the same analysis should be done along this expansion. This, in turn, can cause new side effects to appear along one edge if we have a brick pattern.

The search described in the first pool would be something like this and then I will check for side effects after:

function binarySearch(min, max, resizedElement, otherCoord, vertical=false) {

    function getElement(x, y) {
        if (vertical) {
            let tmp = x;
            x = y;
            y = tmp;
        }
        // we know there will always be an element touching, so this
        // should only throw an error if we pass bad otherCoord
        return document.elementsFromPoint(x, y).filter(e => e !== resizedElement)[0];
    }

    let elements    = new Set(),
        startIndex  = min,
        startElement= getElement(min, otherCoord),
        stopIndex   = max,
        stopElement = getElement(max, otherCoord);

    if (startElement === stopElement) {
        elements.add(startElement);
    } else {
        let middle = Math.floor((stopIndex + startIndex)/2),
            left   = binarySearch(min, middle, resizedElement, otherCoord, vertical),
            right  = binarySearch(middle, max, resizedElement, otherCoord, vertical);
        elements   = new Set([...elements, ...left, ...right]);
    }

    return elements;
}

      

Am I overcomplicating this? Is there a better approach? Is this doable through trees and I just can't see it?

+3


source to share


1 answer


If the underlying structure doesn't change, you can probably solve your css flexbox tree structure problem .

Flexbox is a very powerful layout tool that is native to modern browsers. You are using css to declare your layout using display: flex;

among other simple css.

The flexbox CSS trick The tutorial can explain this much better than I can, please refer to to understand what's going on . The code below is more of a demonstration.

The idea is to change the flexbox styles of the element. To resize the element, change flex-basis

to javascript.
I only have buttons below to show the proof of concept, but ultimately you want to use mouse events to resize the elements. You can divide event.clientX

by the width of the container ( container.clientWidth

) to get a percentage of where the mouse is to the container and use that value for flexbasis.

In the demo below, I am using one variable that I use to keep track of the flexbasis of an element .a

and .a-complement

. When you click the buttons, flexbasis updates for each item. They start at 50% 50% and grow / shrink by 10% each button presses. This example can be extended to cover the resizing of all elements using the same technique. They will all respect each other and they will all have no gaps, etc.

Moral of the story: Let the layout engine do the work for you! Don't use absolute positioning unless you really need to.



To fix problems with tree structure: you could restructure the tree by moving divs to other divs when needed. If that complicates things too much, then unfortunately the browser may not have built-in support for your document structure.

But this may happen in the future ...

If flexbox doesn't solve your problem, a more experimental CSS GRID might, but note that CSS grid is only implemented in the latest browser and mobile browsers, which may be okay with your target audience.

let aBasis = 0.5;
const elementA = document.querySelector('.a');
const aComplement = document.querySelector('.a-complement');
document.querySelector('#left').addEventListener('click', () => {
  aBasis -= 0.1;
  elementA.style.flexBasis = (aBasis * 100) + '%';
  aComplement.style.flexBasis = ((1 - aBasis) * 100) + '%';
  console.log((aBasis * 100) + '%', ((1 - aBasis) * 100) + '%');
});

document.querySelector('#right').addEventListener('click', () => {
  aBasis += 0.1;
  elementA.style.flexBasis = (aBasis * 100) + '%';
  aComplement.style.flexBasis = ((1 - aBasis) * 100) + '%';
  console.log((aBasis * 100) + '%', ((1 - aBasis) * 100) + '%');
});
      

.a {
  display: flex;
  background-color: red;
  flex: 1;
  justify-content: center;
  align-items: center;
}

.b {
  display: flex;
  background-color: blue;
  flex: 1;
  justify-content: center;
  align-items: center;
}

.c {
  display: flex;
  background-color: green;
  flex: 1;
  justify-content: center;
  align-items: center;
}

.d {
  display: flex;
  background-color: yellow;
  flex: 1;
  justify-content: center;
  align-items: center;
}

.e {
  display: flex;
  background-color: orange;
  flex: 1;
  justify-content: center;
  align-items: center;
}

.h-container {
  display: flex;
  align-items: stretch;
  flex: 1;
}

.v-container {
  display: flex;
  flex: 1;
  flex-direction: column;
}

.container {
  display: flex;
  height: 200px;
  width: 200px;
  flex: 1
}

.example-container {
  display: flex;
  width: 100%;
}
      

<div class="example-container">
  <div class="container">
    <div class="h-container">
      <div class="a">
        <span>A</span>
      </div>
      <div class="a-complement v-container">
        <div class="b">
          <span>B</span>
        </div>
        <div class="h-container">
          <div class="v-container">
            <div class="c"><span>C</span></div>
            <div class="e"><span>E</span></div>
          </div>
          <div class="d"><span>D</span></div>
        </div>
      </div>
    </div>
  </div>
  <div>
    <button id="left">move a to the left</button>
    <button id="right">move a to the right</button>
  </div>
</div>
      

Run codeHide result


+1


source







All Articles