jbabey jbabey - 3 months ago 19
CSS Question

Make responsive container snap to elements as it resizes

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:

enter image description here

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:

enter image description here

Good:

enter image description here

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.

Answer

Pure 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 complex 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 (complex 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 become 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 compensate 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;
}
Comments