I'm trying to create a flexible layout in CSS3 with 3 boxes stacked on each other. The boxes are siblings and thus have the same parent element. The height of the first box must fit its content. The height of the following two boxes shall grow to fit their respective content until they're about to overflow their parent. In that case, they shall shrink so that they don't overflow.
The problem is that I can't figure out how to prevent one of the shrinking boxes from becoming crushed if its content is small in relation to the other shrinking box. I want those boxes to shrink down to a certain point where they won't shrink anymore – let's say the equivalent of two rows of text for example. Setting min-width isn't an option because I don't want the boxes to be taller than their content in case the content is only one row for example. If any of the boxes has come to the point where it shall not shrink anymore and the parent can't hold them without overflowing, the parent shall get a scrollbar.
I don't know the content in advance so the layout has to be dynamic. I want to solve this only with CSS, if possible.
Here's an example of the problem where box3 is too small:
p {
margin: 0;
}
.container, .box {
border: 1px solid black;
}
.box {
background-color: white;
margin: 1em;
overflow: auto;
}
#container {
background-color: yellow;
display: flex;
flex-direction: column;
height: 15em;
overflow: auto;
}
#box1 {
flex: 0 0 auto;
}
#box2 {
}
#box3 {
}
<div id="container" class="container">
<div id="box1" class="box">
<p>◼</p>
</div>
<div id="box2" class="box">
<p>◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻</p>
<p>◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻</p>
<p>◼◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻◻</p>
<p>◼◼◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻◻</p>
<p>◼◼◼◼◼◻◻◻◻◻◻◻◻◻◻◻◻◻</p>
<p>◼◼◼◼◼◼◻◻◻◻◻◻◻◻◻◻◻◻</p>
<p>◼◼◼◼◼◼◼◻◻◻◻◻◻◻◻◻◻◻</p>
<p>◼◼◼◼◼◼◼◼◻◻◻◻◻◻◻◻◻◻</p>
<p>◼◼◼◼◼◼◼◼◼◻◻◻◻◻◻◻◻◻</p>
<p>◼◼◼◼◼◼◼◼◼◼◻◻◻◻◻◻◻◻</p>
<p>◼◼◼◼◼◼◼◼◼◼◼◻◻◻◻◻◻◻</p>
<p>◼◼◼◼◼◼◼◼◼◼◼◼◻◻◻◻◻◻</p>
<p>◼◼◼◼◼◼◼◼◼◼◼◼◼◻◻◻◻◻</p>
<p>◼◼◼◼◼◼◼◼◼◼◼◼◼◼◻◻◻◻</p>
<p>◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◻◻◻</p>
<p>◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◻◻</p>
<p>◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◻</p>
<p>◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼</p>
</div>
<div id="box3" class="box">
<p>◼◻◻</p>
<p>◼◼◻</p>
<p>◼◼◼</p>
</div>
</div>
IF I understand your question correctly, the flex-shrink property should be what you are looking for.
Set #box1 to flex-shrink: 0
Set #box2 to flex-shrink: 1
Set #box3 to flex-shrink: 1
Related
Suppose I have a container element that has a set width. I have a content element with a display inline-block that can contain some text. This text could be so big that it has to be filled over several lines. The problem with this is that default behaviour for multiline text is that it grows the element to the complete width of the parent element.
Imagine the following example:
HTML:
<div class="container">
<div class="content">
Firstreallongword11 Secondalsolongword22 Thirdwordthatisconsiderablylonger
</div>
</div>
CSS:
.container {
width: 260px;
border: solid blue 1px;
}
.content {
display: inline-block;
background: red;
}
Because these are long words, they will be positioned over multiple lines. What I would expect for the .content element, is that it would grow to the maximum width of the largest word on one single row.
But as you can see, because it consists of multiple lines, the element grows to the max width of .container .
FIDDLE
What I want to achieve is, .content gaining the width of the largest item on a single row, as it would with one single lin:
FIDDLE
Is there any way to achieve this with pure css/html?
The simple answer is: No, you can't do that with pure CSS.
But here is a solution anyway, which is a bit of a hack: It uses display: table-cell; for the .content element, and a rather small width value (which will adjust to the actual value of the longest word and acts like a min-width setting in this case):
.container {
width: 260px;
border: solid blue 1px;
}
.content {
display: table-cell;
width: 100px;
background: red;
}
<div class="container">
<div class="content">
Firstreallongword11 Secondalsolongword22 Thirdwordthatisconsiderablylonger
</div>
</div>
How can I prevent a child div with scrollbars and flex:1 from exceeding the height of its parent flexbox in Firefox? It works correctly in Chrome.
CodePen link (if you prefer it to Stack Overflow snippets):
https://codepen.io/garyapps/pen/ZMNVJg
Details:
I have a flex container of fixed height. It has a flex-direction:column setting, and it contains multiple childen divs which will get vertically stacked. One of the child divs is given a flex:1 property, whereas others are given fixed heights.
My expectation is that the child div with the flex:1 property will expand to fill the remaining vertical space. This works as expected.
I have also given the child div an overflow-y:scroll property, so that if the content within it exceeds its height, scrollbars appear. This works fine in Chrome. In Firefox however, this child's height increases, exceeding its parent div.
In Chrome:
In Firefox:
As you see in the screenshot, I have two occurrences of this issue, once in the panel on the left, and the other in the panel on the right. In Chrome, the height of the child div remains constant, scrollbars appear, and the height of the parent does not get exceeded. In Firefox scrollbars do not appear, and the height of the child div increases and exceeds its parent.
I have looked at a few other similar questions on SO, and in many cases setting a min-height:0 property solved the problem in Firefox. However I have tried adding min-height:0 to parents, children, both parents and children, and had no luck.
Please run the code snippet below in both Chrome and Firefox to see the difference in the two browsers.
I would appreciate any advice on how to prevent child div from growing.
(Note that Bootstrap 4 is being used. The code snippet references the bootstrap 4 .css file CDN)
Code Snippet:
.body-content {
height: 300px;
max-height:100%;
border: 3px dashed purple;
display:flex;
flex-direction: column;
}
#messagescontainerrow {
flex: 1;
border: 5px double black;
}
#leftdiv {
display:flex;
flex-direction:column;
border: 1px solid green;
}
#messagetools {
height: 50px;
background-color: cornsilk;
}
#messagelist {
flex:1;
overflow-y: scroll;
background-color:whitesmoke;
}
#rightdiv {
display:flex;
flex-direction:column;
border: 1px solid blue;
}
#messagesenderspane {
width: 100%;
height: 50px;
background-color: lemonchiffon
}
#messagecontents {
flex: 1;
overflow-y: scroll;
width: 100%;
border: 1px solid blue;
background-color: aliceblue;
}
#messagesend {
width: 100%;
height: 70px;
background-color: whitesmoke;
}
<html>
<head>
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.2/css/bootstrap.min.css" rel="stylesheet"/>
</head>
<body>
<div class="container body-content">
<div class="row" id="messagescontainerrow">
<div class="col-5" id="leftdiv">
<div id="messagetools">
<input type="button" id="newbutton" value="My Button" />
</div>
<div id="messagelist">
<h4>Chat 1</h4>
<h4>Chat 2</h4>
<h4>Chat 3</h4>
<h4>Chat 4</h4>
<h4>Chat 5</h4>
<h4>Chat 6</h4>
<h4>Chat 7</h4>
<h4>Chat 8</h4>
</div>
</div>
<div class="col-7" id="rightdiv">
<div id="messagesenderspane">
Chat 3
</div>
<div id="messagecontents">
<h4>line 1</h4>
<h4>line 2</h4>
<h4>line 3</h4>
<h4>line 4</h4>
<h4>line 5</h4>
<h4>line 6</h4>
<h4>line 7</h4>
</div>
<div id="messagesend">
<textarea id="sendbox"></textarea>
<input type="button" id="sendbutton" value="Send" />
</div>
</div>
</div>
</div>
</body>
</html>
Short Answer
Instead of flex: 1, use flex: 1 1 1px.
Make these two adjustments in your code:
#messagelist {
/* flex:1; */
flex: 1 1 1px; /* new */
}
#messagecontents {
/* flex:1; */
flex: 1 1 1px; /* new */
}
revised codepen
Explanation
In most cases, as you have noted, adding min-height: 0 to flex items in a column-direction container is enough to correct the problem.
In this case, however, there's an additional obstacle: flex-basis.
You're applying the following rule to flex items #messagelist and #messagecontents: flex: 1.
This is a shorthand rule that breaks down to:
flex-grow: 1
flex-shrink: 1
flex-basis: 0
(source: https://www.w3.org/TR/css-flexbox-1/#flex-common)
2019 UPDATE: Since the posting of this answer in 2018, it appears that Chrome's behavior has changed and is now uniform with Firefox and Edge. Please keep that in mind as you read the rest of this answer.
In Chrome, flex-basis: 0 is enough to trigger an overflow, which generates the scrollbars. (2019 update: This may no longer be the case.)
In Firefox and Edge, however, a zero flex-basis is insufficient. This is probably the more correct behavior in terms of standards compliance as MDN states:
In order for overflow to have an effect, the block-level container must have either a set height (height or max-height) or white-space set to nowrap.
Well, flex-basis: 0 meets none of those conditions, so an overflow condition should not occur. Chrome has probably engaged in an intervention (as they often do).
An intervention is when a user agent decides to deviate slightly from a standardized behavior in order to provide a greatly enhanced user experience.
To meet the "standardized behavior", which would enable an overflow to occur in Firefox and Edge, give flex-basis a fixed height (even if it's just 1px).
I am marking Michael_B's answer as the correct one, since it is a valid solution along with an explanation. In addition here is another solution I came up with, which does not require modifying the flex-basis:
#messagescontainerrow {
flex: 1;
min-height: 0; /* ADDED THIS. */
border: 5px double black;
}
#leftdiv {
display:flex;
flex-direction:column;
max-height: 100%; /* ADDED THIS */
border: 1px solid green;
}
#rightdiv {
display:flex;
flex-direction:column;
max-height: 100%; /* ADDED THIS */
border: 1px solid blue;
}
Explanation:
As per the current Flexbox specification https://www.w3.org/TR/css-flexbox-1/#min-size-auto
In general, the automatic minimum size of a flex item is the smaller
of its content size and its specified size. However, if the box has an
aspect ratio and no specified size, its automatic minimum size is the
smaller of its content size and its transferred size. If the box has
neither a specified size nor an aspect ratio, its automatic minimum
size is the content size.
So, by default #messagescontainerrow was taking on a minimum height based on its contents, rather than respecting the height of its parent flexbox. This behavior can be overridden by setting min-height:0.
By making this change one sees what is displayed in the following image; note that #messagescontainerrow - the one with the double line border - is now the same height as its parent - the one with the purple dashed border.
(Note that the more recent draft specification, found here - https://drafts.csswg.org/css-flexbox/#min-size-auto - says "for scroll containers the automatic minimum size is zero". So in future we might not need to do this).
What remains now is the issue of its children, #leftdiv and #rightdiv, overflowing its borders. As Michael_B pointed out, overflow requires a height or max-height property to be present. So the next step is to add max-height: 100% to both #leftdiv and #rightdiv, so that the overflow-y:scroll property of their children gets triggered.
Here is the result:
How can I prevent a child div with scrollbars and flex:1 from exceeding the height of its parent flexbox in Firefox? It works correctly in Chrome.
CodePen link (if you prefer it to Stack Overflow snippets):
https://codepen.io/garyapps/pen/ZMNVJg
Details:
I have a flex container of fixed height. It has a flex-direction:column setting, and it contains multiple childen divs which will get vertically stacked. One of the child divs is given a flex:1 property, whereas others are given fixed heights.
My expectation is that the child div with the flex:1 property will expand to fill the remaining vertical space. This works as expected.
I have also given the child div an overflow-y:scroll property, so that if the content within it exceeds its height, scrollbars appear. This works fine in Chrome. In Firefox however, this child's height increases, exceeding its parent div.
In Chrome:
In Firefox:
As you see in the screenshot, I have two occurrences of this issue, once in the panel on the left, and the other in the panel on the right. In Chrome, the height of the child div remains constant, scrollbars appear, and the height of the parent does not get exceeded. In Firefox scrollbars do not appear, and the height of the child div increases and exceeds its parent.
I have looked at a few other similar questions on SO, and in many cases setting a min-height:0 property solved the problem in Firefox. However I have tried adding min-height:0 to parents, children, both parents and children, and had no luck.
Please run the code snippet below in both Chrome and Firefox to see the difference in the two browsers.
I would appreciate any advice on how to prevent child div from growing.
(Note that Bootstrap 4 is being used. The code snippet references the bootstrap 4 .css file CDN)
Code Snippet:
.body-content {
height: 300px;
max-height:100%;
border: 3px dashed purple;
display:flex;
flex-direction: column;
}
#messagescontainerrow {
flex: 1;
border: 5px double black;
}
#leftdiv {
display:flex;
flex-direction:column;
border: 1px solid green;
}
#messagetools {
height: 50px;
background-color: cornsilk;
}
#messagelist {
flex:1;
overflow-y: scroll;
background-color:whitesmoke;
}
#rightdiv {
display:flex;
flex-direction:column;
border: 1px solid blue;
}
#messagesenderspane {
width: 100%;
height: 50px;
background-color: lemonchiffon
}
#messagecontents {
flex: 1;
overflow-y: scroll;
width: 100%;
border: 1px solid blue;
background-color: aliceblue;
}
#messagesend {
width: 100%;
height: 70px;
background-color: whitesmoke;
}
<html>
<head>
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.2/css/bootstrap.min.css" rel="stylesheet"/>
</head>
<body>
<div class="container body-content">
<div class="row" id="messagescontainerrow">
<div class="col-5" id="leftdiv">
<div id="messagetools">
<input type="button" id="newbutton" value="My Button" />
</div>
<div id="messagelist">
<h4>Chat 1</h4>
<h4>Chat 2</h4>
<h4>Chat 3</h4>
<h4>Chat 4</h4>
<h4>Chat 5</h4>
<h4>Chat 6</h4>
<h4>Chat 7</h4>
<h4>Chat 8</h4>
</div>
</div>
<div class="col-7" id="rightdiv">
<div id="messagesenderspane">
Chat 3
</div>
<div id="messagecontents">
<h4>line 1</h4>
<h4>line 2</h4>
<h4>line 3</h4>
<h4>line 4</h4>
<h4>line 5</h4>
<h4>line 6</h4>
<h4>line 7</h4>
</div>
<div id="messagesend">
<textarea id="sendbox"></textarea>
<input type="button" id="sendbutton" value="Send" />
</div>
</div>
</div>
</div>
</body>
</html>
Short Answer
Instead of flex: 1, use flex: 1 1 1px.
Make these two adjustments in your code:
#messagelist {
/* flex:1; */
flex: 1 1 1px; /* new */
}
#messagecontents {
/* flex:1; */
flex: 1 1 1px; /* new */
}
revised codepen
Explanation
In most cases, as you have noted, adding min-height: 0 to flex items in a column-direction container is enough to correct the problem.
In this case, however, there's an additional obstacle: flex-basis.
You're applying the following rule to flex items #messagelist and #messagecontents: flex: 1.
This is a shorthand rule that breaks down to:
flex-grow: 1
flex-shrink: 1
flex-basis: 0
(source: https://www.w3.org/TR/css-flexbox-1/#flex-common)
2019 UPDATE: Since the posting of this answer in 2018, it appears that Chrome's behavior has changed and is now uniform with Firefox and Edge. Please keep that in mind as you read the rest of this answer.
In Chrome, flex-basis: 0 is enough to trigger an overflow, which generates the scrollbars. (2019 update: This may no longer be the case.)
In Firefox and Edge, however, a zero flex-basis is insufficient. This is probably the more correct behavior in terms of standards compliance as MDN states:
In order for overflow to have an effect, the block-level container must have either a set height (height or max-height) or white-space set to nowrap.
Well, flex-basis: 0 meets none of those conditions, so an overflow condition should not occur. Chrome has probably engaged in an intervention (as they often do).
An intervention is when a user agent decides to deviate slightly from a standardized behavior in order to provide a greatly enhanced user experience.
To meet the "standardized behavior", which would enable an overflow to occur in Firefox and Edge, give flex-basis a fixed height (even if it's just 1px).
I am marking Michael_B's answer as the correct one, since it is a valid solution along with an explanation. In addition here is another solution I came up with, which does not require modifying the flex-basis:
#messagescontainerrow {
flex: 1;
min-height: 0; /* ADDED THIS. */
border: 5px double black;
}
#leftdiv {
display:flex;
flex-direction:column;
max-height: 100%; /* ADDED THIS */
border: 1px solid green;
}
#rightdiv {
display:flex;
flex-direction:column;
max-height: 100%; /* ADDED THIS */
border: 1px solid blue;
}
Explanation:
As per the current Flexbox specification https://www.w3.org/TR/css-flexbox-1/#min-size-auto
In general, the automatic minimum size of a flex item is the smaller
of its content size and its specified size. However, if the box has an
aspect ratio and no specified size, its automatic minimum size is the
smaller of its content size and its transferred size. If the box has
neither a specified size nor an aspect ratio, its automatic minimum
size is the content size.
So, by default #messagescontainerrow was taking on a minimum height based on its contents, rather than respecting the height of its parent flexbox. This behavior can be overridden by setting min-height:0.
By making this change one sees what is displayed in the following image; note that #messagescontainerrow - the one with the double line border - is now the same height as its parent - the one with the purple dashed border.
(Note that the more recent draft specification, found here - https://drafts.csswg.org/css-flexbox/#min-size-auto - says "for scroll containers the automatic minimum size is zero". So in future we might not need to do this).
What remains now is the issue of its children, #leftdiv and #rightdiv, overflowing its borders. As Michael_B pointed out, overflow requires a height or max-height property to be present. So the next step is to add max-height: 100% to both #leftdiv and #rightdiv, so that the overflow-y:scroll property of their children gets triggered.
Here is the result:
The problem I have involves flex-basis: 0%; and how IE11 handles it when the user reduces the window's width. Could be related to the bug with box-sizing.
I have a variable number of flex-children, and each child is of unknown width. Each child has arbitrary dynamically-generated content. However, some of this content must have the ability to wrap text.
The flex-container itself must have 100% width and must be able to wrap its children (i.e., flex-wrap: wrap).
Let's assume three flex-children are present, with the last one requiring text-wrapping:
<div class="flex-container">
<div class="flex-child">
<div>This text should not overlay other flex-children.</div>
</div>
<div class="flex-child">
<div>This text should not overlay other flex-children.</div>
</div>
<div class="flex-child">
<div class="text-break">This text should break on new lines when the container shrinks down</div>
</div>
</div>
The CSS can be defined as follows:
.flex-container {
display: flex;
flex-wrap: wrap;
width: 100%;
}
.flex-child {
flex-grow: 1;
flex-shrink: 0;
flex-basis: 0%;
white-space: nowrap;
padding: 5px;
}
.text-break {
white-space: pre-wrap;
}
When the window is wide enough, each flex-child should be side-by-side, and the text not collapsed:
When the window is shrunken horizontally, the text in the right-most box should begin to break to new lines:
And when the text can no longer break into more lines, the flex-boxes themselves should begin to break lines:
This works great in most modern browsers but not our friend, IE11. In IE11, when changing the screen size, the text wraps fine, but the flex-children do not wrap at all. Because the flex-children do not wrap, their content overflows into one another:
Changing the flex basis to flex-basis: auto; has the opposite effect: the text will not wrap but the flex-children do, but no content overflows (in this screenshot, the text in the green box should be breaking lines rather than the flex-box breaking into a new line):
Most solutions I have seen require having fixed-length flex-children which I cannot afford to have here because they are dynamically generated. I intentionally did not use the flex shortcut property because of some other non-related issues with it. This answer recommends using _:-ms-fullscreen, :root .IE11-only-class { /* IE11 specific properties */ } which might work if I could get around the text-wrapping issue, but I cannot figure that out.
I should also say that I only need this to work on the latest browsers (technically only on certain versions of Chrome and IE11, but having other browsers and versions work as well is a plus).
Here is a code pen showing the problem in full (view in IE11 and Chrome to see the difference).
Does anyone have any ideas as to how to get around this issue?
Remember, the requirements are:
unknown number of flex-children of unspecified width
certain text must wrap before the flex-boxes wrap
flex-children must wrap once the text can no longer wrap more
Chrome (I am using 59.0.3071.109) and IE11 (I am using 11.0.9600.18816CO) should behave in the same way.
CSS-only solutions are preferred
Thank you.
Update:
A coworker of mine recommended using a separate class for flex-children that do not contain wrappable text. The following HTML and CSS were what he used:
<!--HTML-->
<div class="flex-container">
<div class="flex-child child-1">
<div>This text should not overlay other flex-children.</div>
</div>
<div class="flex-child flex-child-without-textwrap child-2">
<div>This text should not overlay other flex-children.</div>
</div>
<div class="flex-child flex-child-with-textwrap child-3">
<div class="text-break">This text should break on new lines when the container shrinks down</div>
</div>
</div>
And
/*CSS*/
.flex-container {
display: flex;
flex-wrap: wrap;
}
.flex-child {
flex-grow: 1;
flex-shrink: 1;
padding: 5px;
white-space: nowrap;
}
.flex-child-without-textwrap {
flex-basis: auto;
}
.flex-child-with-textwrap {
min-width: 200px;
flex-basis: 0%;
}
.text-break {
white-space: pre-wrap;
}
His solution technically fixed the problem as I presented it here, but I forgot to mention another requirement:
Within a flex-child, there can be any combination of wrappable text and unwrappable text
His solution does not succeed when changing the third flex-child to:
<div class="flex-child flex-child-with-textwrap child-3">
<div class="text-break">This text should break on new lines when the container shrinks down</div>
<div>This text should not break on new lines</div>
</div>
In this case, the text in the second div in the third flex-child flows out of its container, and the text begins wrapping too early.
A codepen showing this almost-working solution can be seen here (note that min-width was removed because it caused further issues on Chrome).
Update 2:
I don't think I was clear enough earlier: any, all, or none of the flex-children may have wrappable text. It all depends on the dynamically-generated content. In the example I gave, only the third flex-child has wrappable text, but that might not always be the case.
Note, this answer were posted prior to the question's 2 updates, which made it partially invalid, though I will leave it for now, someone might need it as is.
After a lot of trial-and-error, I came up with this set up, where I aim to make IE mimic the rest of the browsers behavior as much as possible.
IE need a minimum width on the break-able's parent, so the text won't collapse into 0 width before the element wrap, and as flex-basis needs to be auto in general but 0px on the break-able's parent, the flex-grow's need to be somewhere around 5.
I added the following rules using an IE11 only selector (which I showed in this answer of mine).
_:-ms-fullscreen, :root .flex-child {
flex-basis: auto;
}
_:-ms-fullscreen, :root .child-3 {
flex: 5 0 0px;
min-width: 80px;
}
Updated codepen
Stack snippet
.wrapper {
width: 80%;
}
.flex-container {
display: flex;
flex-wrap: wrap;
width: 100%;
}
.flex-child {
flex: 1 0 0%;
white-space: nowrap;
padding: 5px;
}
_:-ms-fullscreen, :root .flex-child {
flex-basis: auto;
}
_:-ms-fullscreen, :root .child-3 {
flex: 5 0 0px;
min-width: 80px;
}
.text-break {
white-space: pre-wrap;
}
/*Add backgrounds for display*/
.child-1 {
background-color: pink;
}
.child-2 {
background-color: lightblue;
}
.child-3 {
background-color: lightgreen;
}
<div class="wrapper">
<div class="flex-container">
<div class="flex-child child-1">
<div>This text should not overlay other flex-children.</div>
</div>
<div class="flex-child child-2">
<div>This text should not overlay other flex-children.</div>
</div>
<div class="flex-child child-3">
<div class="text-break">This text should break on new lines when the container shrinks down</div>
</div>
</div>
</div>
This is NOT a preferred solution because it uses Javascript. However, it works.
After creating all flex-children, and whenever the screen size changes, check if the width of each flex-child is less than the width of its content (for this to work, the content must be wrapped with something with display: table-cell). If so, add a minimum width to the flex-child equal to the width of its content. Once a min-width is added, the calculation need not be done again, so no longer check when screen size changes. Only do this on IE11.
I used jquery to implement this solution.
HTML:
<div class="flex-container">
<div id="1" class="flex-child child-1">
<div class="content-container">This text should not overlay other flex-children.</div>
</div>
<div id="2" class="flex-child child-2">
<div class="content-container">This text should not overlay other flex-children.</div>
</div>
<div id="3" class="flex-child child-3">
<div class="content-container">
<div class="text-break">This text should break on new lines when the container shrinks down</div>
<div>This text should not break on new lines</div>
</div>
</div>
</div>
CSS:
.flex-container {
display: flex;
flex-wrap: wrap;
width: 100%;
}
.flex-child {
flex-grow: 1;
flex-shrink: 0;
flex-basis: 0%;
white-space: nowrap;
padding: 5px;
}
.text-break {
white-space: pre-wrap;
}
.content-container {
display: table-cell;
}
Jquery:
if (!!window.MSInputMethodContext && !!document.documentMode) {//if IE11
$(".flex-child").each(function (i, elem) {
var $flex_child = $(elem);
var child_id = $flex_child.attr("id");
//add resize event to window
$(window).on("resize." + child_id, function () {
var width_of_flex_child = $flex_child.outerWidth();
var content_width = $flex_child.children(".content-container").outerWidth();
if (width_of_flex_child < content_width) {
$flex_child.css("min-width", content_width + "px");
//remove event
$(window).off("resize." + child_id);
}
}).trigger("resize." + child_id);
});
}
The codepen to view this solution can be found here.
Unfortunately, this solution requires additional overhead when managing resources because of the window event.
I have simple structure of element container of dynamic height and fixed width (Markup below). On one hand the element's background should span the whole window width, on the other the children's size must be limited by the container (Desired layout below). The number of children and their sizes (which are equal on the image only for simplicity) are dynamic.
Is that possible without adding extra container? I want to avoid achieving the desired element content width by setting width on the children, because their number is dynamic and the size relationships become complicated to write unless their total width is already limited by container's width.
Here's a pen to experiment;
Markup
<div class="container">
<div class="child">
<div class="child">
...
</div>
.container {
width: <fixed-width>px;
}
Desired layout (the whitespace between children and container is irrelevant)
One route we can take to solve this is by using viewport width on the parent container padding, to force the children into a box that is only 500px wide (as per your codepen).
The important thing to remember when doing this is that box-sizing:border-box; will need to be set on the container, otherwise the padding goes ballistic.
We do this by using calc, vw and padding.
padding: 20px calc(50vw - /*half of container width*/);
Here's the full expanded code of your container on the linked codepen:
.container {
display: flex;
flex-flow: row nowrap;
justify-content: center;
margin-left: auto;
margin-right: auto;
height: 300px;
width: 100%;
padding: 20px calc(50vw - 250px);
background-color: #acffac;
background-size: 100vw auto;
background-position: center top;
box-sizing: border-box;
}
html {
overflow-y:scroll; /* fixes potential calculation errors caused by scroll bar - thanks to Roberts comment */
}
Here's a working version of the codepen, and for the sake of keeping all my eggs in one basket, here's an expandable code snippet:
.container {
display: flex;
flex-flow: row nowrap;
justify-content: center;
margin-left: auto;
margin-right: auto;
height: 300px;
width: 100%;
padding: 20px calc(50vw - 250px);
background-color: #acffac;
background-size: 100vw auto;
background-position: center top;
box-sizing: border-box;
}
.child {
flex: 1 0 auto;
width: 100px;
height: 100%;
background-color: #ff4444;
}
.child+.child {
margin-left: 20px;
}
<div class="container">
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
</div>
I will finish off by pointing out that if someone else has a better solution, you may want to look at that for time being instead as there is some issues with using vw inside calc on older versions of Chrome and Safari.
EDIT:
As noted in the comments by Vadim and Robert there are a few things that can cause some snags.
Firstly, assuming you are working with a bare minimum template (i.e. no normalize/reset.css), your body will most probably still have the inherent margins that would mess with this kind of layout. You can fix this with:
body {
margin:0;
}
Secondly, depending on your OS (Yes I'm looking at you Microsoft!) your scrollbars can push your content to the side whilst simultaneously still being included in the calculation for vw.
We can fix this one of two way. The first being an adjustment on the padding calculation to include the scrollbar side, but you would have to write a script to ensure that scrollbar is actually present, and scrollbars differ in sizes (I.E -> 17px, Edge -> 12px).
The other alternative would be to use a custom content scroller, which would do a full overflow:hidden; over the content, thereby removing the scroll bar, before implementing it's own version of a scrollbar (which generally lies on top of the content with a position:fixed;) it.
Using vw and flex we can center the child elements and achieve exactly what you require. I have written a JSfiddle where you can check it out.
Basically what I have done is created a container with display set to flex. Using margin property of the first child element, I have centered all of the other child divs and then the regular properties were added to other divs.
Here's the code
body{
margin: 0;
padding: 0;
}
#container{
display: flex;
width: 100vw;
height: 40vw;
background-color: #333333;
align-items: center;
}
.child{
width: 4vw;
height: 80%;
background-color: red;
margin-right: 10vw;
}
.child:first-child{
margin-left: 28vw;
}
<div id="container">
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
</div>