I ran into an interesting CSS problem today, and I have been wracking my brain trying to solve it.
This is similar to the trivial problem of "a row of three elements, with a left, a right, and center," which can be solved easily with flexbox — but it has a couple of caveats that make it (I think) an impossible layout without JavaScript.
The desired goal
Consider a row-like container element and three children, "left", "right", and "center". The children may be of varying widths, but they are all the same height.
"Center" should try to stay centered relative to its container — but the three sibling elements must not overlap, and may push outside the container if necessary.
The markup, then, might look something like this:
<div class="container">
<div class="left">I'm the left content.</div>
<div class="center">I'm the center content. I'm longer than the others.</div>
<div class="right">Right.</div>
</div>
The CSS is where the challenge is.
Examples of what should happen
For wide containers, "center" is centered relative to the container (i.e., its siblings' widths do not matter), as in the image below; notice that midpoint of the "center" element matches the midpoint of the container, and that the left and right "leftover" spaces are not equal:
For narrower containers, "center" abuts the widest sibling, but it does not overlap. The remaining space is distributed only between the narrow sibling and the "center" sibling. Notice also that the container's midpoint, indicated by the caret, is no longer the same as "center's" midpoint:
Finally, as the container continues to shrink, there's no other option but to have all three elements lined up in a row, overflowing the parent:
My attempts to solve this
Surprisingly, I haven't found a good way to implement this in pure CSS.
You'd think flexbox would be the winner, but you can't really get flexbox to do it right: The space-between property distributes the space uniformly between the elements, so the center element doesn't actually end up centered. The flex-grow/shrink/basis properties aren't especially useful for this either, since they're responsible for controlling the size of the child elements, not for controlling the size of the space between them.
Using position:absolute can solve it as long as the container is wide enough, but when the container shrinks, you end up with overlap.
(And float layouts can't get within a mile of getting this right.)
I could combine the best two solutions above, and switch between them with a #media query — if all of the widths were known in advance. But they aren't, and the sizes may vary widely.
In short, there's no pure-HTML-and-CSS solution to this problem that I know of.
Conclusion, and a JSFiddle to experiment with
I created a JSFiddle that shows both the desired goal and a few non-solutions. Feel free to fork it and experiment. You can simulate the container resizing by grabbing the bar to the left of the content and dragging it. You are allowed to rearrange/restructure the HTML and CSS, if rewriting it gets you closer to a working answer.
https://jsfiddle.net/seanofw/35qmdnd6
So does anyone have a solution to this that doesn't involve using JavaScript to intelligently distribute the space between the elements?
With flexbox you should be able to solve that, by giving the left/right elements flex: 1 and the right text-align: right.
The main trick is flex: 1, which will make them share available space equally.
For more versions, see this brilliant question/answer, flexbox-justify-items-and-justify-self-properties
Fiddle snippet
Stack snippet
body {
font: 14px Arial;
}
.container {
display: flex;
border: 1px solid #00F;
}
.container > div > span {
display: inline-block;
background: #36F;
white-space: nowrap;
padding: 2px 4px;
color: #FFF;
}
.container > .center > span {
background: #696;
}
.container .left,
.container .right {
flex: 1;
}
.container .right {
text-align: right;
}
.center-mark {
text-align: center;
font-size: 80%;
}
.note {
text-align: center;
color: #666;
font-style: italic;
font-size: 90%;
}
<div class="container">
<div class="left">
<span>
I'm the left content.
</span>
</div>
<div class="center">
<span>I'm the center content. I'm longer than the others.</span>
</div>
<div class="right">
<span>
Right.
</span>
</div>
</div>
<div class="center-mark">^</div>
<div class="note">(centered marker/text)</div>
Related
I'm having a problem getting long lines of text to correctly break and wrap in a chat feature that I'm working on. The HTML below is the relevant set of nested elements, but the crux of the biscuit is with .chat__messages__item and .chat__messages__body (the whole block below is inside of an element that is set to 24vw, so it is all intended to be window-width-responsive).
First off, here's the HTML/CSS...
<style>
.chat__messages__inner{
display: table;
height: 100%;
width: 100%;
}
.chat__messages__list {
display: table-cell;
vertical-align: bottom;
margin: 0;
padding: 10px 20px 0;
list-style-type: none;
}
.chat__messages__item {
position: relative;
margin-bottom: 10px;
padding: 8px 10px;
background-color: #D8F2FD;
clear: both;
}
<!-- THIS STYLE HAS NO AFFECT UNLESS I SET A MAX-WIDTH ON .chat__messages__item -->
.chat__messages__body {
word-wrap: break-word;
}
</style>
<div class='chat__messages__inner'>
<ul class='chat__messages__list'>
<li class='chat__messages__item'>
<div class='chat__messages__body'>
hereisaverylonglineoftextthatiwouldliketobreakandwrap
</div>
</li>
<li class='chat__messages__item'>
<div class='chat__messages__body'>
here is a long sentence that will wrap and behave correctly
</div>
</li>
</ul>
</div>
The desired behavior is that the <div> and <li> containing the text should be no wider/taller than the text itself, but those elements also should never be wider than their parents - so for a few words, they might be 150px wide, but if the container shrinks to be less than 150px, these elements will also start to shrink and the text inside will start to wrap.
Playing with this code, I was able to get close to the desired result by setting the style for .chat__messages__body to include word-wrap: break-word and then setting the parent .chat__messages__item to include max-width: 300px (omitted above). Although the long word would break and wrap, it only produced the correct result on my full-screen window - if the window is resized or starts off at less-than-full, the word still wraps but the div is 300px wide (I tried setting this as a percentage, but that does not work, the word actually unwraps).
The long sentence that I included above does exactly what I would like - its parent <div> and <li> are both the size of the text, and if the window shrinks so that the width of these elements would be greater than their parents (which all scale to the 24vw ancestor), they begin to shrink as well and the text wraps on spaces.
In plain English, I would like the long word's container to never be wider than the 100% width ancestors, and it needs to resize dynamically with the window, breaking and wrapping along the way.
I'm not really a CSS/design expert, this is code I inherited from someone else, and I've been fighting with this for way too long... any guidance would be much appreciated.
Here is a question you could check out. One of the answers suggest what I would try, which is to use the <wbr> tag which creates a word break opportunity. You can read about it here.
Ok, turns out the thing to do was to set .chat__messages__inner and .chat__messages__list to display: inline-block with width: 100%, and .chat__messages__item needed to have max-width: 100%.
I have a single-line fixed-width container div with two variable-width span inside. Any overflow of the first span should be hidden with an ellipsis. The second span floats on the right and should be shown in full. Please see this Fiddle:
<div class='container'>
<span class='left'>Long long variable stuff</span>
<span class='right'>Changing stuff</span>
</div>
I want the first span's width to dynamically adjust according to the width of the second span so that both span stay on the same line. How can I do that?
You can use Flexbox, so with flex: 1 on .right, .left will adjust its size and overflow will be hidden.
.container {
width: 200px;
display: flex;
border: 1px solid black;
}
.left {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.right {
flex: 1;
white-space: nowrap;
}
<div class='container'>
<span class='left'>Long long variable stuff</span>
<span class='right'>Changing stuff</span>
</div>
I don't think there's any way for CSS to dynamically know the length of an element without javascript. If you're looking for a purely CSS solution you're going to need to give it some direction in order for it to know the widths you want. Visually, that might be a bit of a compromise for you, but it will allow you to ensure that everything is always on one line.
For the solution I'm about to propose to work you need to know one width of the two. In this case I'm going to say that you can make a best guess of the "changing stuff" on the right.
Your first problem is that spans are inline elements by default - not inline-block. In order to get the overflow text property to work, you need to use it with an inline-block or block element.
The next piece is to use calc. Calc excepts mixed measurements so you can subtract an exact pixel value off of a percent. This works really well for responsive layouts.
I've created an updated version of your plunker to illustrate:
https://jsfiddle.net/n19ddahb/5/
For a webpage grid-layout I decided to use Flexbox. Now I wanted to implement some "auto-functionality", so that grid-boxes can later be inserted without the need to add classes or styles in the HTML. One of this features is to make a box allways be 75% as tall as it is wide - even if the box is resized by, for example, browserwindow resize. Off course, if the boxes content extends the 75%-height, it should (and only then should) increase its height to fit the content. I searched for hours to find a suitable solution, but I finally got it working. So I thought at least, until I added content to the box.
The auto aspect-ratio works fine, as long as the box is empty. If I add content, the 75% of the width is allways added to the height it has through extension by its content. I made a jsfiddle to clearly visualize the problem:
JSFiddle wd5s9vq0, visualizing the following Code:
HTML-Code:
<div class="container">
<div class="content-cell"></div>
<div class="content-cell"></div>
</div>
<div class="container">
<div class="content-cell">
This cell has an inreased height because of
it's content. The empty space below the
content is the 75% of the cells width.
</div>
<div class="content-cell"></div>
</div>
CSS:
.container {
display: flex;
width: 400px;
}
.content-cell {
flex: 1 1 0;
margin: 10px;
background-color: #ccc;
}
.content-cell::after {
content: "";
display: block;
padding-top: 75%;
}
If I didn't knew it better, it looks like a floating-problem - but I think the ::before / ::after selector should add the block-element before the element it is used on and not inside it.
Does anyone has an idea on how to fix this problem?
This seems to be a very widespread problem on the internet, and most solutions you find are either about wrapping the content, absolute-positioning the content or a mixture of both. This has numerous and case-dependent downsides. After hours of playing around with the code, I finally found a combination of CSS proporties that work without the need to add any DOM or make the content absolute-positioned. This looks quit basic, and I am wondering why it took me so long and why you can't find it out there on the web.
The HTML:
<div class="mybox aspect-full">
This is text, that would normally extend the box downwards.
It is long, but not so long that it extends the intended aspect-ratio.
</div>
The CSS:
.mybox {
width: 200px;
}
.aspect-full::before {
content: '';
display: block;
padding-top: 100%;
float: left;
}
The only downside I could find is that the content of your cell must float. If you use clear on one of your child objects, it is positioned below the expander-block and you are back to the original problem. If you need to clear the floating of divs inside of these aspect-ratio-cells, you might consider to wrap them and keep the wrapper floatable.
This question already has answers here:
Why doesn't the height of a container element increase if it contains floated elements?
(7 answers)
Closed 8 years ago.
I make simple http://jsfiddle.net/6KzXw/ with CSS:
.container {
width: 50%;
margin: 0 auto;
padding: 2px;
background: red;
}
.left {
float: left;
background: yellow;
}
.right {
float: right;
background: yellow;
}
and HTML:
<div class="container">
<div class="left">To the left.</div>
<div class="right">To the right.</div>
</div>
I wondering why area of container isn't red....
After search I found solution with overflow: hidden but official docs about fix: http://www.w3.org/TR/CSS21/visufx.html make me cry when I try to understand how it work...
Can any explain why overflow: hidden is fix for surrounding problem with standard in mind?
overflow: hidden causes the container to establish a block formatting context for its contents. Without it, the floats participate in some other formatting context, having been taken out of normal flow, and therefore the floats are not taken into account when calculating the height of the container.
Once you cause the container to establish a formatting context, it will consider the floats, even though the floats are still taken out of the normal flow that is established within its own formatting context. That is stated in another section of the spec. The reason this isn't stated in the section that you link to is because it's a side effect that was never really intended, but made so due to implementation limits. See this answer (and the one that it links to) for an explanation.
you need to provide a height to the div as if you float the contents, the contents are removed from the flow of the page. essentially the div sees no children inside it as the children are floating.
i added a height to the div height: 20px
FIDDLE
When you apply the 'hidden' property to an element, any floats within it will take up space. So if you have a container that only contains floated elements, that container will act like it's empty. By setting 'overflow' to 'hidden' we force that container to account for those floats.
Another solution to this is to add a "clearfix" element below the floats. It might look something like this:
<div class="container">
<div class="left">To the left.</div>
<div class="right">To the right.</div>
<div class="clearfix"></div>
</div>
And the CSS will be something like this:
.clearfix {
clear: both;
}
Personally, I prefer setting overflow to hidden (if possible) but there are many clearfix solutions out there.
http://nicolasgallagher.com/micro-clearfix-hack/
http://css-tricks.com/snippets/css/clear-fix/
http://learnlayout.com/clearfix.html
Edit:
As far as setting a set height. You can do that if you want a set height, but if you want the container to grow or shrink based on the height on the floats, you need to set overflow hidden or use a clearfix.
Because the container has a height of 0
I have two DIVs that I need to position exactly on top of each other. However, when I do that, the formatting gets all screwed up because the containing DIV acts like there is no height. I think this is the expected behavior with position:absolute but I need to find a way to position these two elements on top of each other and have the container stretch as the content stretches:
The top left edge of .layer2 should be exactly aligned to the top left edge of layer1
<!-- HTML -->
<div class="container_row">
<div class="layer1">
Lorem ipsum...
</div>
<div class="layer2">
More lorem ipsum...
</div>
</div>
<div class="container_row">
...same HTML as above. This one should never overlap the .container_row above.
</div>
/* CSS */
.container_row {}
.layer1 {
position:absolute;
z-index: 1;
}
.layer2 {
position:absolute;
z-index: 2;
}
Actually this is possible without position absolute and specifying any height. All You need to do, is use display: grid on parent element and put descendants, into the same row and column.
Please check example below, based on Your HTML. I added only <span> and some colors, so You can see the result.
You can also easily change z-index each of descendant elements, to manipulate its visibility (which one should be on top).
.container_row{
display: grid;
}
.layer1, .layer2{
grid-column: 1;
grid-row: 1;
}
.layer1 span{
color: #fff;
background: #000cf6;
}
.layer2{
background: rgba(255, 0, 0, 0.4);
}
<div class="container_row">
<div class="layer1">
<span>Lorem ipsum...<br>Test test</span>
</div>
<div class="layer2">
More lorem ipsum...
</div>
</div>
<div class="container_row">
...same HTML as above. This one should never overlap the .container_row above.
</div>
First of all, you really should be including the position on absolutely positioned elements or you will come across odd and confusing behavior; you probably want to add top: 0; left: 0 to the CSS for both of your absolutely positioned elements. You'll also want to have position: relative on .container_row if you want the absolutely positioned elements to be positioned with respect to their parent rather than the document's body:
If the element has 'position: absolute', the containing block is established by the nearest ancestor with a 'position' of 'absolute', 'relative' or 'fixed' ...
Your problem is that position: absolute removes elements from the normal flow:
It is removed from the normal flow entirely (it has no impact on later siblings). An absolutely positioned box establishes a new containing block for normal flow children and absolutely (but not fixed) positioned descendants. However, the contents of an absolutely positioned element do not flow around any other boxes.
This means that absolutely positioned elements have no effect whatsoever on their parent element's size and your first <div class="container_row"> will have a height of zero.
So you can't do what you're trying to do with absolutely positioned elements unless you know how tall they're going to be (or, equivalently, you can specify their height). If you can specify the heights then you can put the same heights on the .container_row and everything will line up; you could also put a margin-top on the second .container_row to leave room for the absolutely positioned elements. For example:
http://jsfiddle.net/ambiguous/zVBDc/
Here's another solution using display: flex instead of position: absolute or display: grid.
.container_row{
display: flex;
}
.layer1 {
width: 100%;
background-color: rgba(255,0,0,0.5); /* red */
}
.layer2{
width: 100%;
margin-left: -100%;
background-color: rgba(0,0,255,0.5); /* blue */
}
<div class="container_row">
<div class="layer1">
<span>Lorem ipsum...</span>
</div>
<div class="layer2">
More lorem ipsum...
</div>
</div>
<div class="container_row">
...same HTML as above. This one should never overlap the .container_row above.
</div>
Great answer, "mu is too short".
I was seeking the exact same thing, and after reading your post I found a solution that fitted my problem.
I was having two elements of the exact same size and wanted to stack them.
As each have same size, what I could do was to make
position: absolute;
top: 0px;
left: 0px;
on only the last element. This way the first element is inserted correctly, "pushing" the parents height, and the second element is placed on top.
Hopes this helps other people trying to stacking 2+ elements with same (unknown) height.
Here's some reusable css that will preserve the height of each element without using position: absolute:
.stack {
display: grid;
}
.stack > * {
grid-row: 1;
grid-column: 1;
}
The first element in your stack is the background, and the second is the foreground.
I had to set
Container_height = Element1_height = Element2_height
.Container {
position: relative;
}
.ElementOne, .Container ,.ElementTwo{
width: 283px;
height: 71px;
}
.ElementOne {
position:absolute;
}
.ElementTwo{
position:absolute;
}
Use can use z-index to set which one to be on top.
Due to absolute positioning removing the elements from the document flow position: absolute is not the right tool for the job. Depending on the exact layout you want to create you will be successful using negative margins, position:relative or maybe even transform: translate.
Show us a sample of what you want to do we can help you better.
Of course, the problem is all about getting your height back. But how can you do that if you don't know the height ahead of time? Well, if you know what aspect ratio you want to give the container (and keep it responsive), you can get your height back by adding padding to another child of the container, expressed as a percentage.
You can even add a dummy div to the container and set something like padding-top: 56.25% to give the dummy element a height that is a proportion of the container's width. This will push out the container and give it an aspect ratio, in this case 16:9 (56.25%).
Padding and margin use the percentage of the width, that's really the trick here.
After much testing, I have verified that the original question is already right; missing just a couple of settings:
the container_row MUST have position: relative;
the children (...), MUST have position: absolute; left:0;
to make sure the children (...) align exactly over each other, the
container_row should have additional styling:
height:x; line-height:x; vertical-align:middle;
text-align:center; could, also, help.