I have a dynamic width container div that contains constant width items. I'd like to be able to resize the container so that it only ever shows whole items, never cutting the item on the right in pieces.
JSFiddle
For example, a user's screen may render showing 5 items:
If that user were to start shrinking the width of their screen, as soon as the bar is no longer wide enough to hold 5 full items I would like it to shrink down to only showing 4 items.
Bad:
Good:
I know this is possible to achieve by using CSS3 media queries, but I'd like to avoid writing a different breakpoint for every single different number of elements. I'd also like to avoid using a javascript resize
event handler, though I am not sure if this is possible without it.
I have a dynamic width container div that contains constant width items. I'd like to be able to resize the container so that it only ever shows whole items, never cutting the item on the right in pieces.
JSFiddle
For example, a user's screen may render showing 5 items:
If that user were to start shrinking the width of their screen, as soon as the bar is no longer wide enough to hold 5 full items I would like it to shrink down to only showing 4 items.
Bad:
Good:
I know this is possible to achieve by using CSS3 media queries, but I'd like to avoid writing a different breakpoint for every single different number of elements. I'd also like to avoid using a javascript resize
event handler, though I am not sure if this is possible without it.
- I think you're going to need something more plex, perhaps needing JS. Maybe there is a library out this that supports this. – TravisO Commented Sep 24, 2013 at 19:17
- @Enzino same issue as Stefan Henze's answer. this causes a big empty space on the right side of the container. i want the container to shrink when it can't fit another element. – jbabey Commented Sep 24, 2013 at 20:00
4 Answers
Reset to default 14 +250Pure CSS (some limitations)
This solution is based off a modification to another solution for a similar problem I gave elsewhere.
Here is the fiddle.
It involves a plex relationship of overlapping pseudo-elements to create the borders, which can cause the solution to have certain limitations on what may or may not be able to be done within it (plex backgrounds would be an issue, as well as a necessity for certain positioning aspects). Nevertheless, it functions in the given case.
A Bit of Explanation
Essentially, each .item
element is building its own section of top/bottom borders using both the :after
and :before
elements, the former tied to the .itemContainer
, the latter tied to the .item
itself (the :before
is needed to create the last bit of border at the end of the row). Additionally, the :before
is also creating the "flexible" position of the right border to give it the responsiveness needed when an element shifts out of view. This is why the :before
must be related to the .item
itself, and also why each :after
element's background must be used to "hide" the right border of the preceding :before
element.
Since we don't know via css the "count" at any given point as to which element is the "last" in the display, all the :before
elements must be displayed, but we don't want right borders for them all, hence why the :after
needs to cover them. As an element shifts down to the next line, its :after
no longer covers the right border of what has now bee the last displayed element, revealing that border to be used as the "right" border of the whole group.
HTML (Matching your original fiddle)
<div class="itemBar">
<div class="itemContainer">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
<div class="item">6</div>
<div class="item">7</div>
<div class="item">8</div>
<div class="item">9</div>
<div class="item">10</div>
</div>
</div>
CSS of main items
.itemBar {
display: inline-block;
width: 50%; /* some width can be set, does not need to be this */
}
.itemContainer {
position: relative; /* :after pseudo-elements are positioned off this */
z-index: 1; /* needed for pseudo-element interaction */
overflow: hidden;
display: inline-block;
max-height: 68px;
width: 100%;
border-left: 1px solid black; /* left border is supplied by this */
}
.item {
width: 60px;
height: 62px;
display: inline-block;
margin: 2px;
border: 1px solid black;
/* NOTE: CANNOT be given positioning */
}
CSS of Pseudo Elements
.item::after {
content: '';
position: absolute; /* will position off itemContainer */
z-index: -1; /* push it to the background */
top: 0; /* set it to top of itemContainer */
bottom: 0; /* set it to bottom of itemContainer */
margin-left: -100%; /* shove it past the far left edge of itemContainer */
/* next, use padding to bring it back to its position at the end
of the text string of .item */
padding-left: 100%;
/* next, add enough padding on the right to pensate for the right
padding, right margin, and right border of .item */
padding-right: 3px;
/* next, create the top and bottom border of "container",
in conjunction with the :before; so this is a pseudo-border for
.itemContainer being created by the .item elements */
border-top: 1px solid black;
border-bottom: 1px solid black;
background: #fff; /* hide other :before borders */
}
.item:before { /* make right border */
content: '';
padding-top: 66px; /* give it .itemContainer height minus border heights */
width: 100%;
margin-top: -3px; /* .item top margin + border width */
margin-left: -100%; /* pull the text in .item back into position */
margin-right: 0;
/* next, push this behind the background with an even lower z-index
to hide it if it is not the right most element beign used to
form the right border */
z-index: -2;
float: right; /* get the before element to the right */
position: relative; /* needs to be adjusted in position */
right: -4px; /* move it same as padding-right of the after element */
display: block; /* give it a display */
/* next, use it to build the fake right border and also the fake
final top/bottom borders of the of itemContainer */
border-right: 1px solid black;
border-top: 1px solid black;
border-bottom: 1px solid black;
}
Remove:
white-space: nowrap;
from .itemBar
and add
text-align:justify;
to .itemContainer
This way, other items which does not fit there will fall to next line yet the space will be distributed equally.
Demo: http://jsfiddle/rDxRt/4/
You could have the items in the container float. This way, they will float onto the next line if the container gets to small.
If you are lucky enough and know the height of the items, you can set the container to a fixed height and overflow: hidden
to make the items that flow to the next line not show up.
jsfiddle example
Here's another solution that's little simpler than ScottS's. It doesn't require any positioning or use of ::before
or ::after
, instead relying on the fact that ::first-line
doesn't stretch past the last visible item.
The markup
<div class="itemContainer">
<div class="item">1</div><div
class="item">2</div><div
class="item">3</div><div
class="item">4</div><div
class="item">5</div><div
class="item">6</div><div
class="item">7</div><div
class="item">8</div><div
class="item">9</div><div
class="item">10</div>
</div>
Note that this solution is sensitive to inter-element whitespace, so you need to remove newlines/spaces between consecutive .item
s. I've line-wrapped between the tag name and class above to make it bit more readable.
The CSS
.itemContainer {
overflow: hidden;
height: 70px; /* .item's offset height + margin */
/* you can constrain the width of this any way you like */
}
.itemContainer::first-line {
font-size: 70px; /* needs to be at least as big as .wrap height */
line-height: 0px; /* fixes oddities in Firefox */
background: black; /* "border" color */
}
.item {
display: inline-block;
vertical-align: text-top; /* this minimizes the font-size required above */
margin: 10px; /* include desired "border" size of .itemContainer */
box-shadow: 0 0 0 9px white; /* spread = desired margin */
background: white; /* needs an opaque background */
/* reset font-size and line-height */
font-size: 1rem;
line-height: 1.5;
/* set your item size and borders however you like */
height: 50px;
width: 50px;
border: 1px solid red;
box-sizing: border-box;
}
.item::first-line {
font-size: 1rem; /* fix IE precedence */
}
.item + .item {
margin-left: 0; /* inline-block margins don't collapse */
}
Result
Tested in Safari, Chrome, Firefox, and IE 11.