2

I have a flexbox container that can be resized by dragging. How can I prevent that container from overflowing (getting too small and starting to hide some items) or underflowing (getting too large and showing blank space)?

In the following example, the container should stop shrinking when all items reach 18px height (except the last one), and stop expanding when all items reach their maximal heights (no blank space should appear).

const resizable = document.querySelector('.flex');
let startHeight = NaN;
let startY = NaN;

resizable.addEventListener('mousedown', e => {
  startHeight = resizable.getBoundingClientRect().height;
  startY = e.pageY;
});

window.addEventListener('mousemove', e => {
  if (isNaN(startY) || isNaN(startHeight)) {
    return;
  }
  
  resizable.style.height = (startHeight + e.pageY - startY) + 'px';
});

window.addEventListener('mouseup', () => {
  startHeight = startY = NaN;
});
.flex {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid black;
  cursor: ns-resize;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

.item-1 {
  background: red;
  height: 40px;
  max-height: 60px;
  flex: 1;
}

.item-2 {
  background: blue;
  flex: none;
}

.item-3 {
  background: green;
  height: 50px;
  max-height: 50px;
  flex: 1;
}

.item-4 {
  background: yellow;
  height: 30px;
  flex: none;
}
<div class="flex">
  <div class="item-1">
    Item 1
  </div>
  <div class="item-2">
    Item 2
  </div>
  <div class="item-3">
    Item 3
  </div>
  <div class="item-4">
    Item 4
  </div>
</div>

A pure CSS solution would be preferred. The solution should allow shrinking the container so that all the items are at their minimal size, and expanding it so that all of them occupy their maximal size. Hardcoding a minimal and maximal height in the container's CSS is not acceptable. I am seeking a generic solution.

matteodelabre
  • 443
  • 5
  • 13
  • Have you seen this library? http://isotope.metafizzy.co/ – Gokhan Dilek Jul 24 '16 at 13:34
  • 1
    I did not know about it, but at first glance it looks like a library for Masonry-like layouts, doesn't it? Could you elaborate on how this could solve the problem? Thanks. :) – matteodelabre Jul 24 '16 at 13:55
  • With your flex, set the min-height: 100px; max-height: 200px; If you want more advanced stuff, you can use the library I suggested :) – Gokhan Dilek Jul 24 '16 at 14:02
  • I would like to avoid hardcoding the height in the CSS because the content can change dynamically. If you think that Isotope can dynamically compute the minimal and maximal heights, could you expand that to an answer? – matteodelabre Jul 24 '16 at 14:09
  • Is this what you are trying to accomplish? http://stackoverflow.com/questions/33080/setting-the-height-of-a-div-dynamically/33147#33147 – Gokhan Dilek Jul 24 '16 at 14:29
  • I'd like the container to not be able to be resized in such a way that: 1) its content is hidden (too small); 2) its height is too tall for its content (and a white zone appears under it). The question you linked doesn't seem to achieve that. – matteodelabre Jul 24 '16 at 14:37
  • 1
    @matteodelabre it's not possible to have a CSS-only solution since the resizing is handled in JavaScript. As the answer below points out, it's much easier and _preferable_ to relax the constraints of this task and just set a guard in the JavaScript resize function without doing anything special to the CSS. – Patrick Roberts Jul 27 '16 at 13:47

1 Answers1

1

I don't think a pure CSS solution would be possible here since you set the height absolutely using JavaScript.

You could just add a guard like I've done in the snippet below. You could also calculate the bottom height when the resizable size is zero and when it's huge and introduce boundaries based on that (probably better performance but bad with dom changes)

const resizable = document.querySelector('.flex');
const lastChild = resizable.querySelector(':last-child');
const resizableBorderBottom = 1;  // hardcoded - could be computed too
let startHeight = NaN;
let startY = NaN;

resizable.addEventListener('mousedown', e => {
  /* 
   * here we assume that the bottom of the last child
   * is the same as the bottom of the resizable for simplicity
   */
  startHeight = resizable.getBoundingClientRect().height;
  startY = e.pageY;
});

window.addEventListener('mousemove', e => {
  if (isNaN(startY) || isNaN(startHeight)) {
    return;
  }
  const lastHeight = resizable.style.height;
  resizable.style.height = ((startHeight + e.pageY - startY) | 0) + 'px';
  const lastChildBottom = lastChild.getBoundingClientRect().bottom | 0;
  const resizableBottom = (resizable.getBoundingClientRect().bottom | 0) - resizableBorderBottom;
  // check if we need to revert the change
  if (lastChildBottom !== resizableBottom) {
    resizable.style.height = lastHeight;
  }
});

window.addEventListener('mouseup', () => {
  startHeight = startY = NaN;
});
.flex {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid black;
  cursor: ns-resize;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

.item-1 {
  background: red;
  height: 40px;
  max-height: 60px;
  flex: 1;
}

.item-2 {
  background: blue;
  flex: none;
}

.item-3 {
  background: green;
  height: 50px;
  max-height: 50px;
  flex: 1;
}

.item-4 {
  background: yellow;
  height: 30px;
  flex: none;
}
<div class="flex">
  <div class="item-1">
    Item 1
  </div>
  <div class="item-2">
    Item 2
  </div>
  <div class="item-3">
    Item 3
  </div>
  <div class="item-4">
    Item 4
  </div>
</div>
mash
  • 2,466
  • 11
  • 17