Home » Html » Make last element take remaining width with wrapping (and with IE9 support)

Make last element take remaining width with wrapping (and with IE9 support)

Posted by: admin November 29, 2017 Leave a comment

Questions:

I’m looking for a way to align multiple items next to each other, having them wrap once they fill up the line, and the last item take up the remaining width of the last line.

I have found multiple questions on a similar topic before, with solutions generally making the items use float: left to align them on a line, and overflow: hidden on the last element which makes it take up the remaining space automatically. Those solutions work fine in a single line, but they stop working once there are enough items before the last element, that they wrap into multiple lines. Then, the “last” element is still rendered in the first row (if there is enough space), making it no longer be the last element.

However, I want the last element to stay the last element at all times, to have it in the last row—whatever row that is—and automatically take up the remaining space there.

This is pretty simple with a wrapping flexbox, since you just need to put flex: 0 auto on the former items to make them take up whatever space they need (without taking more), and flex: 1 on the last element to make it take up the remainder. However, I need to support Internet Explorer 9, so flexbox is unfortunately out of the question.

This is how the situation looks like. When running the snippet, you can use the “Toggle flex box” button to toggle flexbox mode which shows the way it should look like.

* { box-sizing: border-box; }
.container {
  width: 300px;
  background: snow;
  padding: 5px;
  overflow: auto;
}
.element {
  float: left;
  margin: 2px 5px 2px 0;
  background: lavender;
}
.last {
  overflow: hidden;
}
.last > input {
  width: 100%;
}

/* Working solution with flex box */
.flex .container {
  display: flex;
  flex-wrap: wrap;
}
.flex .element {
  flex: 0 auto;
}
.flex .last {
  flex: 1;
}
<div class="container">
  <div class="element">Foo</div>
  <div class="element">Foo bar</div>
  <div class="element">Foo bar baz</div>

  <div class="last"><input type="text" /></div>
</div>

<div class="container">
  <div class="element">Foo</div>
  <div class="element">Foo bar</div>
  <div class="element">Foo bar baz</div>
  <div class="element">Foo</div>
  <div class="element">Foo bar</div>
  <div class="element">Foo bar baz</div>

  <div class="last"><input type="text" /></div>
</div>

<button onclick="this.parentNode.classList.toggle('flex')">Toggle flex box</button>
Answers:

Here is the solution:

  • Add a <div style="clear:left;margin-top:22px"></div> before the .last element*
  • Add margin-top: -22px; to .last where the amount is about the same as the line-height
  • If you dont want it to get too small add min-width to .last and it will work as you’d expect
    Also min-width: 1px; is requiered in the class to avoid it getting 0 in special cases.

Tested and working in IE8+, Edge, Chrome, Opera, Firefox

* { box-sizing: border-box; }
.container {
  width: 300px;
  background: snow;
  padding: 5px;
  overflow: auto;
}
.element {
  float: left;
  margin: 2px 5px 2px 0;
  background: lavender;
}
.last {
  overflow: hidden;
  margin-top: -22px;
  min-width: 1px;
}
.last > input {
  width: 100%;
}
.container .unwrap {
  clear: left;
  margin-top: 22px
}
<div class="container">
  <div class="unwrap"></div>
  <div class="last"><input type="text" /></div>
</div>

<div class="container">
  <div class="element">Foo</div>
  <div class="element">Foo bar</div>
  <div class="element">Foo bar baz</div>
  <div class="unwrap"></div>
  <div class="last"><input type="text" /></div>
</div>

<div class="container">
  <div class="element">Foo</div>
  <div class="element">Foo bar</div>
  <div class="element">Foo bar baz boo</div>
  <div class="unwrap"></div>
  <div class="last" style="min-width: 100px">
    <input type="text" placeholder="min-width 100px" />
  </div>
</div>

<div class="container">
  <div class="element">Foo</div>
  <div class="element">Foo bar</div>
  <div class="element">Foo bar baz</div>
  <div class="element">Foo</div>
  <div class="element">Foo bar</div>
  <div class="element">Foo bar baz</div>
  <div class="unwrap"></div>
  <div class="last"><input type="text" /></div>
</div>

<div class="container">
  <div class="element">Foo</div>
  <div class="element">Foo bar</div>
  <div class="element">Foo bar baz</div>
  <div class="element">Foo baaaaaaaaaaaaaaaaaaaaaaar</div>
  <div class="unwrap"></div>
  <div class="last"><input type="text" /></div>
</div>

<div class="container">
  <div class="element">Foo bar baz Foo bar baz Foo bar baz Foo bar baz Foo bar baz Foo bar baz Foo bar baz Foo bar baz Foo bar baz Foo bar baz Foo bar baz Foo bar baz</div>
  <div class="unwrap"></div>
  <div class="last"><input type="text" /></div>
</div>

* margin-top:22px only to avoid the input to slip over in case there are no elements in the container. It should be the absolute value of the negative margin of the input

Questions:
Answers:

The following solution isn’t perfect, it’s using the position hacks. If you disable overflow: hidden; you’ll see each <input> actually has the same width as the container. But with some CSS visual tweaks, it looks good and may work ok.

Issue 1: if you type in more characters than it can visually fit, the whole view shifts to the left.

Workaround 1: set a maxlength value to the <input> might help.

jsfiddle

* {
  box-sizing: border-box;
}
.container {
  width: 300px;
  background: snow;
  padding: 5px;
  position: relative;
  overflow: hidden; /*hide the overflow*/
}
span:after {
  content: "
* { box-sizing: border-box; } .container { width: 300px; background: snow; padding: 5px; position: relative; overflow: hidden; /*hide the overflow*/ } span:after { content: "\00A0" /*just for one no-break space*/ } input { width: 100%; position: absolute; /*without any top/right/bottom/left value*/ border: 2px solid grey; border-width: 0 0 2px 0; } input:focus { outline: 0; }
A0" /*just for one no-break space*/ } input { width: 100%; position: absolute; /*without any top/right/bottom/left value*/ border: 2px solid grey; border-width: 0 0 2px 0; } input:focus { outline: 0; }
<div class="container">
  <span>FooFoo barFoo bar baz</span>
  <input type="text" />
</div>

<div class="container">
  <span>FooFoo barFoo bar bazFooFoo barFoo bar baz bla</span>
  <input type="text" />
</div>

Questions:
Answers:

And for everybody else, who would like a script option to work with, here is one for you too.

Just add data-calcattr to the elements you want to have its width property set to width: calc(100% - ??px);, where ??px is its current width.

Side note:

The script would likely need adjustments to make up for future borders, margins etc., so this one serves more as one way how to do it.

function calcattr() {
  var els = document.querySelectorAll('[data-calcattr]');
  // TODO: add switch to enable calc. different properties like
  //       get the remaining height as well, by checking the 
  //       data-calcattr value, i.e.: data-calcattr='height'
  for (i = 0; i < els.length; i++) {
    els[i].setAttribute("style", "width: 0");    
    var lpos = parseInt(els[i].getBoundingClientRect().left);
    var minw = parseFloat(getComputedStyle(els[i]).getPropertyValue('min-width'));
    var calcwidth = "calc(100% - " + (lpos + 2) + "px)";
    els[i].setAttribute("style", "width: " + calcwidth);
    if ((els[i].getBoundingClientRect().right - lpos) < minw) {
      els[i].setAttribute("style", "width: 100%");
    }
  }
}

window.onload = calcattr;

(function() {
  window.addEventListener("resize", resizeThrottler, false);
  var resizeTimeout;
  function resizeThrottler() {
    if ( !resizeTimeout ) {
      resizeTimeout = setTimeout(function() {
        resizeTimeout = null;
        actualResizeHandler();
      }, 66);
    }
  }
  function actualResizeHandler() {
    calcattr();
  }
}());
* { box-sizing: border-box; }
.container {
  width: 300px; //70%;
  background: snow;
  padding: 5px;
  overflow: auto;
}
.fullwidth {
  width: 100%;
}
.element {
  float: left;
  margin: 2px 5px 2px 0;
  background: lavender;
}
.last > input {
  width: 100%;
}
.last {
  float: left;
  min-width: 20px;
}
<div class="container">
  <div class="element">Foo</div>
  <div class="element">Foo bar</div>
  <div class="element">Foo bar baz</div>

  <div class="last" data-calcattr><input type="text" /></div>
</div>

<div class="container">
  <div class="element">Foo</div>
  <div class="element">Foo bar</div>
  <div class="element">Foo bar baz</div>
  <div class="element">Foo</div>
  <div class="element">Foo bar</div>
  <div class="element">Foo bar baz</div>
  <div class="element">Foo</div>
  <div class="element">Foo bar</div>
  <div class="element">Foo bar baz</div>

  <div class="last" data-calcattr><input type="text" /></div>
</div>

<div class="container fullwidth">
  <div class="element">Foo</div>
  <div class="element">Foo bar</div>
  <div class="element">Foo bar baz</div>
  <div class="element">Foo</div>
  <div class="element">Foo bar</div>
  <div class="element">Foo bar baz</div>
  <div class="element">Foo</div>
  <div class="element">Foo bar</div>
  <div class="element">Foo bar baz</div>
  <div class="element">Foo</div>
  <div class="element">Foo bar</div>
  <div class="element">Foo bar baz</div>
  <div class="element">Foo</div>
  <div class="element">Foo bar</div>
  <div class="element">Foo bar baz</div>

  <div class="last" data-calcattr><input type="text" /></div>
</div>