5

I need to stack two div elements horizontally side-by-side, where the right one should always adjust its size automatically to its content (up to a max given width) and the left one should just use the space that's left.

Something like this: enter image description here

So far no problem. I've managed to do this by floating the right div to the right and setting the overflow of the left one to hidden:

HTML:

<div class="frame">
    <div class="right">
        I adjust my with to my content but still need to know my boundaries if I'm floated right (max-width)
    </div>

    <div class="left">
        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
    </div>
</div>

CSS:

.frame {
    background-color: lightgreen;
    width: 80%;
    padding: 12px;
    overflow: hidden;
}

.left {
    overflow: hidden;

    background-color: #709670;
    border: 1px solid black;
}

.right {
    float: right;
    max-width: 200px;

    background-color: #127212;
    color: white;
    border: 1px solid black;
}

The challenge is that I want the two divs to be stacked vertically when the page is displayed on a small screen (or small browser window, ...) like this:

enter image description here

Basically I thought this could be done by a simple media query:

@media screen and (max-width: 700px) {
    .right {
        float: none;
        max-width: none;
    }
}

The only problem with this is that the right div (the one that resizes according to its content) needs to be created BEFORE the left one in the markup to make the floating thing work. As a result, it appears above the left one in the "small" layout:

enter image description here

Here's a working example (just use the middle resizer to switch between the "small" and "large" layout): Example on JSFiddle

I've already tried to accomplish the layout with the usage of display: table and table-cell instead of floating, but this way I fail to make the right column as large as its content and still have a working max-width set.

suamikim
  • 5,350
  • 9
  • 40
  • 75

3 Answers3

6

As said in this stackoverflow post, Flexbox can help you if you don't want to resort to jQuery because you only need to support a single modern browser: Mobile Safari (Edit I also found the height of the left box in IE doesn't work right in my Fiddle, I have NO idea why; it should).

For you that would be: This awesome fiddle!

Edit Since you want the right div to adjust to it's content, you want this example instead

CSS:

.frame {
    position:relative;
    background-color: lightgreen;
    width: 80%;
    padding: 12px;
    overflow:hidden;
    flex-flow:row wrap;

    -webkit-justify-content: flex;
    -moz-justify-content: -moz-flex;
    justify-content: flex;

    display: -webkit-box; /* Safari */ 
    display: -moz-box; /* Firefox 21- */
    display: -ms-flexbox; /* IE 10+ */
    display: -webkit-flex; /* Chrome */
    display: flex; /* Standard (Firefox 22+) */

    -webkit-box-orient: horizontal;
    -moz-box-orient: horizontal;
    -webkit-box-direction: normal;
    -moz-box-direction: normal;
    -ms-flex-direction: row;
    -webkit-flex-direction: row;
    flex-direction: row;
}

.left {    
    -ms-flex-order: 1;     
    -webkit-order: 1;
    order:1;

    -webkit-box-flex: 2;      /* OLD - iOS 6-, Safari 3.1-6 */
    -moz-box-flex: 2;         /* OLD - Firefox 19- */
    -webkit-flex: 2;          /* Chrome */
    -ms-flex: 2;              /* IE 10 */
    flex: 2;                  /* NEW, Spec - Opera 12.1, Firefox 20+ */

    background-color: #709670;
    border: 1px solid black;
    width: auto;
}

.right {    
    -ms-flex-order: 2;
    -webkit-order: 2;
    order:2;

    -webkit-box-flex: 1;      /* OLD - iOS 6-, Safari 3.1-6 */
    -moz-box-flex: 1;         /* OLD - Firefox 19- */
    -webkit-flex: 1;          /* Chrome */
    -ms-flex: 1;              /* IE 10 */
    flex: 1;                  /* NEW, Spec - Opera 12.1, Firefox 20+ */

    background-color: #127212;
    color: white;
    border: 1px solid black;
    width: auto;

    -webkit-box-ordinal-group: 2;   /* OLD - iOS 6-, Safari 3.1-6 */
    -moz-box-ordinal-group: 2;      /* OLD - Firefox 19- */
    -ms-flex-order: 2;              /* TWEENER - IE 10 */
    -webkit-order: 2;               /* NEW - Chrome */
    order: 2;                       /* NEW, Spec - Opera 12.1, Firefox 20+ */
}

@media screen and (max-width: 700px) {
    .frame {
        -webkit-box-orient: vertical;
        -moz-box-orient: vertical;
        -webkit-box-direction: normal;
        -moz-box-direction: normal;
        box-orient: vertical;
        -ms-flex-direction: column;
        -webkit-flex-direction: column;
        flex-direction: column;
    }
}

I'm extremely happy with my result, I feel you will be to! I believe it has nearly full (Mobile Safari doesn't support it) support!

Community
  • 1
  • 1
Zach Saucier
  • 24,871
  • 12
  • 85
  • 147
  • Updated to fully functional now! Only thing you might want to add is a max-height on the `.left` and `.right` classes, but that's up to you and easy to add – Zach Saucier Jul 01 '13 at 23:08
  • First of all thanks for the input. Haven't heard of flexbox before and it's good to be aware of it! Unfortunately in your example the right div does not really adjust to it's content but has a fixed size of 200. It grow automatically smaller if it has less content in it. See this update of your fiddle to see what i mean: http://jsfiddle.net/3vgng/10/ – suamikim Jul 02 '13 at 06:54
  • Edit: The sentence before the last in my previous comment should be: "It SHOULD grow automatically smaller if it has less content in it." – suamikim Jul 02 '13 at 07:03
  • Easy, [here](http://jsfiddle.net/Zeaklous/3vgng/11/) it is. Just changed a couple values – Zach Saucier Jul 02 '13 at 12:27
  • @Zeaklous Perfect. Your last example seems to be exactly what i wanted. I have to check it in my "real life" scenario and accept your answer afertwards! Thanks a lot – suamikim Jul 02 '13 at 13:03
  • @suamikim It's my pleasure, I learned things myself in the process – Zach Saucier Jul 02 '13 at 13:05
  • I've taken the liberty of "cleaning" up your code a little to only declare the css properties that are really needed and used in this fiddle: http://jsfiddle.net/3vgng/16/. IMHO it's a little easier to read this way and at least works in Chrome 27 and Firefox 22 which is all i need. – suamikim Jul 05 '13 at 08:58
3

A CSS/jQuery Based Approach

Assuming that the HTML and content can be adjusted as follows, with the .left element appearing ahead of the .right element:

<div class="frame">
    <div class="left">Lorem ipsum dolor sit amet, c...</div>
    <div class="right">
        <div class="color-wrap">I adjust my width ...</div>
    </div>
</div>

You could use the following CSS for wide screens:

.frame {
    background-color: lightgreen;
    width: 80%;
    padding: 12px;
    overflow: auto;
}
.left {
    background-color: #709670;
    border: 1px solid black;
    display: table-cell;
}
.left .color-wrap {
}
.right {
    display: table-cell;
    width: 200px;
    vertical-align: top;
}
.right .color-wrap {
    background-color: #127212;
    color: white;
    border: 1px solid black;
    display: inline-block;
}

and for the smaller screens:

@media screen and (max-width: 700px) {
    .left {
        display: block;
    }
    .right {
        display: block;
        width: auto;
    }
    .right .color-wrap {
        display: block;
        width: auto;
        color: yellow;
    }
}

For the large screen, you can use display: table-cell and either set a width for the .right element or use a combination of max-width and min-width depending on how you want to deal with the end-case of a single line of text.

If you need to background color to paint only the text area, then use a wrapper element such as .color-wrap and apply the background color to it instead of the table-cell parent element.

For the smaller screens, reset the display property to block and for .right set width: auto.

This is a relatively simple approach with only a single constraint related to dealing with the case of .right having a single line of text.

Dealing With Short Lines on the Right - Adjust Width

To allow the .right block to shrink-to-fit single-line content, you need to adjust the width of the .right table-cell.

This can be done using the following jQuery:

var rightCellMaxWidth = parseInt($(".right").css("width"));

function fixRightCellWidth() {
    $(".right .color-wrap").each(function () {
        var rightCellWidth = $(this).outerWidth();
        var rightCellType = $(this).css("display");
        if (rightCellWidth < rightCellMaxWidth 
            && rightCellType == "inline-block") {
            $(this).parent().width(rightCellWidth);
        } else {
            $(this).parent().removeAttr("style");
        }
    });
}

$(window).resize(function () {
    fixRightCellWidth();
});

fixRightCellWidth();

The trick is to get the width of the element .right .color-wrap. You also need to check the display-type to see what media mode you are in, inline-block for large screens and block for smaller screens.

For larger screens, set the right table-cell element width to that of the .color-wrap child. For single line text, this will be less than 200px, and for multi-line, the width will be the max-width value of 200px.

You also need to remove the inline style to clear the width setting for either the multi-line case or the small screen case.

Demo fiddle: http://jsfiddle.net/audetwebdesign/N4KE2/

Design Comment

Without JavaScript/jQuery, the page will degrade gracefully to a fixed-width right element and the page will still be usable.

Marc Audet
  • 46,011
  • 11
  • 63
  • 83
  • The OP wanted the `right` div to be declared before the left but appear after on small screens, your solution doesn't address that at all – Zach Saucier Jul 01 '13 at 17:36
  • @Zeaklous Thank you for bringing up a relevant point. It was not clear to me from the OP if the content had to be structured that way. However, I decided to put the answer out there to get some feedback. Someone else may find it useful. – Marc Audet Jul 01 '13 at 18:03
  • @Zeaklous That was a misunderstanding: I don't want the right div to be declared before the left one. That was just necessary to make my layout work. Actually I'd prefer to have a layout that has the elements declared in their "correct" order and still work – suamikim Jul 02 '13 at 06:57
  • @Marc Audet Unfortunately in your example the right div does not really adjust its width to its content but has a fixed width of 200px. It should grow automatically smaller if it has less content in it. See this update of your fiddle to see what i mean: http://jsfiddle.net/N4KE2/3/ – suamikim Jul 02 '13 at 07:00
0

I can't think of any pure CSS solution, but if you're inclined to use jquery, you can use the append method like this :

$('.frame').append($('.right'));

This will only move your 'right' block at the end of the 'frame' children.

There's many solutions to get screen / window / frame width, depending on your browser.

Trajan
  • 339
  • 2
  • 15
  • Thanks for the tip. If I don't find no other way around this I think I'm gonna go with JQuery. But still I'd prefer a CSS only solution because there are possibly a lot of those blocks (with left & right elements) on my page that I'd have to switch and I'm afraid that so much dom manipulation costs a lot of performance... – suamikim Jul 02 '13 at 07:07