Is it possible to automatically "flex-shrink" only elements with large content? - css

In a container with property display: flex; and a fixed size, is it possible to only shrink child elements which would take more than the space available if we evenly distributed it? It would be trivial knowing the content sizes beforehand but I'm looking for a solution which works for dynamic content without resorting to Javascript.
Here's an example:
.container {
display: flex;
width: 200px;
}
.item {
flex: 0 1 auto;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Making it prettier -- unrelated to the question */
.container {
background-color: #cde;
padding: 5px;
}
.item {
margin: 0;
}
.item:not(:last-of-type) {
margin-right: 5px;
}
.item:nth-child(1) {
background-color: #dec;
}
.item:nth-child(2) {
background-color: #ced;
}
.item:nth-child(3) {
background-color: #dce;
}
<div class="container">
<p class="item">sm</p>
<p class="item">paragraph with large content</p>
<p class="item">another large content</p>
</div>
We can see that for the first item the letter m has been partly cut out (it shrunk). Given that this item is small enough (I'll properly define this below) I would want to prevent it from shrinking, like this:
The logic I'm aiming for is this: if we distribute the container width evenly between n child elements each one would have childWidth = parentWidth/n; then those that have width (based on its content) less than or equal to this value don't shrink while the other ones do shrink if necessary (it may be the case that the available space allows for all items to be fully displayed).
Is this possible with CSS only?

Ok, I have tested out my comment and it works! Hurray!
This works because it sets the minimum width of the element to the minimum width that can fit the content.
I have set it for only the first item, like you wanted:
.container {
display: flex;
width: 200px;
}
.item {
flex: 0 1 auto;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Making it prettier -- unrelated to the question */
.container {
background-color: #cde;
padding: 5px;
}
.item {
margin: 0;
}
.item:not(:last-of-type) {
margin-right: 5px;
}
.item:nth-child(1) {
background-color: #dec;
min-width: min-content;
}
.item:nth-child(2) {
background-color: #ced;
}
.item:nth-child(3) {
background-color: #dce;
}
<div class="container">
<p class="item">sm</p>
<p class="item">paragraph with large content</p>
<p class="item">another large content</p>
</div>

Related

Change text alignement upon flex break

I'm writing a form that's supposed to be responsive, that is, when the browser window is small the left label "jumps" on top (see example below by removing the text-align property and resizing the browser).
Right now, it works well when the label text is left aligned.
For usuabilities issue, I'd like the label to be right aligned (I don't want them too far from the input box) but as soon as the flex has wrapped, I don't want them to be right aligned anymore.
Example code: https://jsfiddle.net/xhtfqbzL/
.cont {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
span
{
flex: 1 0 20vw;
text-align: right;
display: inline-block;
padding-right: 0.5em;
}
input
{
flex: 1 1 20rem;
display: inline-block;
}
<div class='cont'>
<span>Label</span>
<input type="text" placeholder="content">
</div>
So how to achieve this effect ?
Here is a hack to be used with caution (or not used at all ...)
.cont {
display: flex;
flex-wrap: wrap;
overflow: hidden;
}
.cont:before {
content: "";
flex: 1 0 20vw;
height: 1.2em;
}
span {
flex: 1 1 20rem;
position: relative;
}
span:before,
span:after {
content: attr(data-text);
position: absolute;
padding-right: 0.5em;
}
span:before {
left: 0;
bottom: 100%;
}
span:after {
top: 0;
right: 100%;
}
input {
width: 100%;
}
<div class='cont'>
<span data-text="Label">
<input type="text" placeholder="content">
</span>
</div>
Right now, my best attempt is this:
html, body { padding: 0; margin: 0 }
.cont
{
display: flex;
flex-wrap: wrap;
flex-direction: row;
}
.cont span, .cont input
{
display: inline-block;
}
.cont span
{
box-sizing: border-box;
text-align: right;
background: blue;
flex: 1 0 20rem;
}
#media screen and (max-width: 40rem) {
.cont span { text-align: left; }
}
.cont input
{
background: green;
flex: 1 1 50vw;
box-sizing: border-box;
}
<div class='cont'>
<span>Some Label</span>
<input type="text" value="Text here">
</div>
I did not want to have media queries because there's always the pain to find the breaking point (and this can evolve based on the design changes or device evolutions). Here, I've still used media queries but the 40rem magic value is computed, not selected by hand.
Typically, one of the form's row item has a fixed size, the other being relative to the viewport size. In this example, the fixed size is 20rem.
Since they are flex items, they'll wrap when they can't fit anymore on this row, that is when their width will reach their flex-basis property.
Thus, the width of the row is span_width[20rem] + input_width[50% * w] = 100% * w
I'm deducing that they will wrap when 100% w = 50% w + 20rem => 50%w = 20rem => w = 40rem
So they'll wrap when the viewport's width becomes lower than 40rem.
The main issue with this is that you must know the exact margin & padding size around such items and this is a real pain to maintain.
Another solution which is not using media queries:
html, body { padding: 0; margin: 0 }
#test
{
display: flex;
align-items: center;
flex-wrap: wrap;
flex-direction: row;
}
#test p
{
flex: 0 1 20rem;
padding:0;
margin:0;
}
#test p s
{
width: calc((48vw - 100%) * 5000);
min-width: 1%;
max-width: 16rem;
display: inline-block;
background: salmon;
height: 1rem;
}
#test p span
{
background: red;
padding-right: 0.5rem;
}
#test b
{
flex: 1 0 50vw;
background: yellow;
}
<div id="test">
<p>
<s></s>
<span>First</span>
</p>
<b>Second</b>
</div>
This works this way:
The label is split in 2 inline elements, a spacer and the label itself.
The spacer is using the Fab Four trick, that is, the width property is calculated to oscillate between +infinite and -infinite in order to be constrained by either min-width and max-width.
The breakpoint is set by the 2nd item in the flex's row (in the example, window_width:100vw - input_width:50vw): when the size left for the label is smaller than the minimum width of the row, its max-width property is used and this adds a "space" push the label element to the right. When the flex row wraps, the size left is now very large and above the breakpoint, and thus the min-width is selected (in my example, I've used 1% but it can be 0%, that is, almost no "margin" on the left of the label).
The caveat of this technic is that you must add a element (could probably be done with a ::before pseudo element here) and you must set a max-width less than the parent width - label width.
That point makes this solution is a pain to maintain.

CSS div dynamic height

I have two divs as shown below (A and B):
Section B is has an input field with max-height of 100px (as an example) and overflow-y auto: This way, the input field will only be certain height.
.section_B{
position: absolute;
bottom: 0;
width: 100%;
}
.section_B_input{
max-height: 100px;
overflow-y: auto;
}
Because Section B's height can be anywhere in between 20px and 100px (for example), the section A's height needs to be dynamic and is depended on Section B height.
I read that display:flex can be used somehow, but I am not sure how to.
Any suggestions?
Thanks!
The technique with flexbox is to add flex-grow: 1; to the element you want to have a dynamic height. Here is a quick example.
* {margin:0;padding:0;box-sizing:border-box;}
html,body,.flex {
min-height: 100vh;
}
.flex {
display: flex;
flex-direction: column;
}
.a {
flex-grow: 1;
background: #eee;
}
.b {
background: #333;
}
section {
padding: 2em;
}
input {
transition: padding .5s;
}
input:focus {
padding: 2em;
}
<div class="flex">
<section class="a">
</section>
<section class="b">
<input>
</section>
</div>

Keep an element centred between, or above, two other elements

I've managed to get CSS3 to almost do what I want:
The grey .top-middle container is of arbitrary width, and must always remain flush with the top edge of the parent container (the <main>).
The purple left and right containers are also of arbitrary width, and must always remain flush with their respective edges of the parent container.
When the parent container is sufficiently wide, the three top containers should sit side-by-side; otherwise, the left and right containers should sit just beneath the .top-middle container. (It would be nice if the two purple containers dropped at the same time, but I'll live with one of them remaining next to the middle container, when there's space.)
The minimum width of the <main> container should essentially be the width of the .top-middle container
The .top-middle container should be centred, ideally relative to the parent container (<main>), but at least relative to the horizontal space available to it (between the left and right containers)
It's that last requirement, #5, that I haven't managed.
I'd prefer not to resort to JavaScript, and of course, it needs to be a cross-browser solution. (I don't really care about IE < 11, though—people using that cruft have bigger worries than whether my CSS looks pretty!)
N.B. This issue cannot (AFAIK) be tested in jsfiddle, StackOverflow's code snippet feature, or any other fixed-width environment. Please copy my code and paste it into a file, and watch what happens when you widen or narrow the browser window.
Here's my complete code, for your copying and pasting pleasure:
main div {
white-space: nowrap;
color: white;
}
.top-container {
background: olive;
}
.top-middle {
background: grey;
}
.top-left,
.top-right {
background: purple;
}
.bottom-container {
background: blue;
text-align: center;
}
/* The crux of the layout starts here */
main {
height: 300px;
display: flex;
flex-direction: column;
}
.top-container>* {
display: inline-block;
padding: 1em;
}
.top-left {
float: left;
}
.top-right {
float: right;
}
.spacer {
background: silver;
flex-grow: 1;
}
<main>
<div class="top-container">
<div class="top-middle">
This should be centred, and always stay on top.
<em>Left</em> and <em>right</em> should drop when the window contracts.
</div>
<div class="top-left">left</div>
<div class="top-right">right</div>
</div>
<div class="spacer"></div>
<div class="bottom-container">This is the bottom.</div>
</main>
Your fifth requirement can be achieved by making the following change:
Add text-align: center; to .top-container
This works because text-align effects the alignment of inline elements:
The text-align CSS property describes how inline content like text is aligned in its parent block element. text-align does not control the alignment of block elements itself, only their inline content.
text-align (https://developer.mozilla.org/en-US/docs/Web/CSS/text-align)
.top-middle is set to be inline-block due to .top-container>* { display: inline-block; padding: 1em; } so will be centered within .top-container.
main div {
white-space: nowrap;
color: white;
}
.top-container {
background: olive;
text-align: center;
}
.top-middle {
background: grey;
}
.top-left,
.top-right {
background: purple;
}
.bottom-container {
background: blue;
text-align: center;
}
/* The crux of the layout starts here */
main {
height: 300px;
display: flex;
flex-direction: column;
}
.top-container>* {
display: inline-block;
padding: 1em;
}
.top-left {
float: left;
}
.top-right {
float: right;
}
.spacer {
background: silver;
flex-grow: 1;
}
<main>
<div class="top-container">
<div class="top-middle">
This should be centred, and always stay on top.
<em>Left</em> and <em>right</em> should drop when the window contracts.
</div>
<div class="top-left">left</div>
<div class="top-right">right</div>
</div>
<div class="spacer"></div>
<div class="bottom-container">This is the bottom.</div>
</main>
Unfortunately, there is a drawback with this method as a bug in Firefox will always force .top-left and .top-right onto a new line:
Also affecting Bugzilla itself, as glob pointed out on IRC. https://bugzilla.mozilla.org/show_bug.cgi?id=677757#c4 has the
"comment 4" text hidden
The cause is this code in nsLineLayout::ReflowFrame:
if (psd->mNoWrap) {
// If we place floats after inline content where there's
// no break opportunity, we don't know how much additional
// width is required for the non-breaking content after the float,
// so we can't know whether the float plus that content will fit
// on the line. So for now, don't place floats after inline
// content where there's no break opportunity. This is incorrect
// but hopefully rare. Fixing it will require significant
// restructuring of line layout.
// We might as well allow zero-width floats to be placed, though.
availableWidth = 0;
}
I wonder whether the right thing to do is:* not manipulate the
available width at all, or* make the available width infinite, since
the nowrap content is never going to wrap around the float anyway
(In theory, the correct solution is not to try placing the float until
the following break opportunity. I wonder if other browsers do that.)
Bug 488725 - float pushed down one line with white-space: nowrap; (https://bugzilla.mozilla.org/show_bug.cgi?id=488725)
You could also use flex to center .top-middle although this wont push both .top-left and .top-right down to a new line when the space is taken up. To achieve this make the following changes:
Add display: flex; to .top-container to make its children use the flexbox model
Add flex-wrap: wrap; to .top-container to make its children wrap onto new lines when they run out of space
Add justify-content: space-between; to .top-container to add space around the child elements which will center .top-middle
main div {
white-space: nowrap;
color: white;
}
.top-container {
background: olive;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.top-middle {
background: grey;
}
.top-left,
.top-right {
background: purple;
}
.bottom-container {
background: blue;
text-align: center;
}
/* The crux of the layout starts here */
main {
height: 300px;
display: flex;
flex-direction: column;
}
.top-container>* {
display: inline-block;
padding: 1em;
}
.top-left {
float: left;
}
.top-right {
float: right;
}
.spacer {
background: silver;
flex-grow: 1;
}
<main>
<div class="top-container">
<div class="top-left">left</div>
<div class="top-middle">
This should be centred, and always stay on top.
<em>Left</em> and <em>right</em> should drop when the window contracts.
</div>
<div class="top-right">right</div>
</div>
<div class="spacer"></div>
<div class="bottom-container">This is the bottom.</div>
</main>
To get both .top-left and .top-right moving onto a new line when they run out of space you will need to use JavaScript. The following is using vanilla JavaScript but should provide a good starting point. In principle:
We check to see if the combined width of .top-left, .top-middle and .top-right is equal to or bigger than .top-container
If it is then we change the order of .top-left and .top-right to place them after .top-middle. We add margin to .top-left to force it onto a new line and margin to .top-middle to center it (as there are no elements in the same line for justify-content: space-between; to work)
If it isn't we set the styles back to the defaults
var topContainer = document.getElementsByClassName('top-container')[0];
var topLeft = document.getElementsByClassName('top-left')[0];
var topMiddle = document.getElementsByClassName('top-middle')[0];
var topRight = document.getElementsByClassName('top-right')[0];
var topContainerWidth;
var topLeftWidth = topLeft.offsetWidth;
var topMiddleWidth = topMiddle.offsetWidth;
var topRightWidth = topRight.offsetWidth;
var moveDivs;
(moveDivs = function(){
topContainerWidth = topContainer.offsetWidth;
if ((topLeftWidth + topMiddleWidth + topRightWidth) >= topContainerWidth) {
topLeft.style.order = 1;
topLeft.style.marginRight = topRightWidth + 'px';
topMiddle.style.margin = '0 auto';
topRight.style.order = 2;
} else {
topLeft.style.order = 0;
topLeft.style.marginRight = 0;
topMiddle.style.margin = 0;
topRight.style.order = 2;
}
})();
window.addEventListener('resize', function(event){
moveDivs();
});
main div {
white-space: nowrap;
color: white;
}
.top-container {
background: olive;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.top-middle {
background: grey;
}
.top-left,
.top-right {
background: purple;
}
.bottom-container {
background: blue;
text-align: center;
}
/* The crux of the layout starts here */
main {
height: 300px;
display: flex;
flex-direction: column;
}
.top-container>* {
display: inline-block;
padding: 1em;
}
.top-left {
float: left;
}
.top-right {
float: right;
}
.spacer {
background: silver;
flex-grow: 1;
}
<main>
<div class="top-container">
<div class="top-left">left</div>
<div class="top-middle">
This should be centred, and always stay on top.
<em>Left</em> and <em>right</em> should drop when the window contracts.
</div>
<div class="top-right">right</div>
</div>
<div class="spacer"></div>
<div class="bottom-container">This is the bottom.</div>
</main>
I rearranged your markup and used display: table-cell. Here's the fiddle example http://jsfiddle.net/k108vt58/. Hope this helps and is what you're looking for.

Flexbox - how to control height proportional to width?

How do I control the height of a flexbox so that it stays proportional to the width as the element grows?
I want the height of .inner to remain proportional to a given ratio as its width changes.
All examples of flexbox I've seen either holds the height constant when the width changes, or grows enough to contain its contents.
(haml)
.outer
.inner
%img
.inner
.inner
Perhaps the example will be helped if we include an image within it... or maybe not. just throwing an idea out there.
(sass)
.outer {
display: flex;
.inner {
flex: 1 1 auto;
}
}
There is no method specific to flexbox that would manage this.
There is well known padding-bottom trick that would permit this but it requires a pseudo-element (for preference) and an internal absolutely positioned div to hold the content.
Reference Web Link
As you will appreciate, absolute positioning is somewhat inflexible so laying out your content would be the main issue.
Applying this to flexbox:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.outer {
display: flex;
width: 500px;
margin: 1rem auto;
align-items: flex-start;
}
.inner {
flex: 1 1 auto;
border: 1px solid grey;
position: relative;
}
.inner:before {
content: "";
display: block;
padding-top: 100%;
/* initial ratio of 1:1*/
}
.content {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
/* otehr ratios */
.ratio2_1:before {
padding-top: 50%;
}
.ratio1_2:before {
padding-top: 200%;
}
.ratio4_3:before {
padding-top: 75%;
}
.ratio16_9:before {
padding-top: 56.25%;
}
<div class="outer">
<div class="inner">
<div class='content '>Aspect ratio of 1:1</div>
</div>
<div class="inner ratio2_1">
<div class='content'>Aspect ratio of 2:1</div>
</div>
<div class="inner ratio16_9">
<div class='content'>Aspect ratio of 16:9</div>
</div>
</div>

How can I ensure that an element will not cause a wrap in a flexbox model?

Let's take the following fiddle : http://jsfiddle.net/wQP8p/
<div class="card">
<div class="preview"></div>
<div class="name">Super Long Title Because I Want To Do It So</div>
<div class="extra">OK</div>
</div>
And the CSS
.card {
display: flex;
flex-wrap: wrap;
width: 200px;
}
.preview {
width: 100%;
padding-top: 45%;
background: red;
}
.name {
flex: auto;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
background: yellow;
}
.extra {
flex: none;
background: blue;
}
What can I do to keep the ellipsis, and put the blue element at the left of the yellow one ?
Unfortunately, Flexbox doesn't work that way. If you want the flex items to appear side by side, you have to give them an appropriate flex-basis value that will allow them to fit within the flex container when wrapping is enabled (flex-shrink doesn't do anything here either).
http://jsfiddle.net/wQP8p/2/
.name {
flex: 1 80%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
background: yellow;
}
.extra {
flex: 1 20%;
background: blue;
}
If the content is dynamic (ie. you don't know the actual width to set), you'll have to add a wrapper around these elements.
You could put the .extra div within the .name div and apply display:inline-block; to the .extra class, as shown in this JSFiddle.
Edit: This may not be a best practice, but it seems to achieve what you are looking for aesthetically.

Resources