Uncollapse a vertical margin in two adjacent elements - css

There are multiple ways posted here to uncollapse a vertical PARENT margin, but nothing about uncollapsing vertical margins of adjacent elements. The only solution I found was in this answer (back in 2009):
<div style="overflow: hidden; height: 0px; width: 0px;"> </div>
Almost 7 years passed since there. Is some better way to do this (possibly using some CSS3)?
Basically, suppose you have: http://jsfiddle.net/ok2u3o3c/
<div class="one"></div>
<div class="two"></div>
div {
width: 300px;
height: 200px;
}
.one {
margin-bottom: 10px;
background-color: blue;
}
.two {
margin-top: 20px;
background-color: red;
}
What would be the most elegant way to make the distance between these 2 boxes 30px instead of 20px (where the first margin contributes 10px and doesn't collapses)?

Let's start with the relevant documentation explaining the behavior of collapsing margins:
8 Box model - 8.3.1 Collapsing margins
In CSS, the adjoining margins of two or more boxes (which might or might not be siblings) can combine to form a single margin. Margins that combine this way are said to collapse, and the resulting combined margin is called a collapsed margin.
The following rules apply, which means that there are a things that you can do to prevent the margins from collapsing for sibling elements:
Margins between a floated box and any other box do not collapse (not even between a float and its in-flow children)
Therefore if you float the elements with collapsing margins, they will no longer collapse:
.collapsing-margins {
margin: 100px 0;
background: #f00;
float: left;
width: 100%;
}
<div class="parent">
<div class="collapsing-margins">Element</div>
<div class="collapsing-margins">Element</div>
</div>
Margins of inline-block boxes do not collapse (not even with their in-flow children).
Therefore you could also add change the display of the elements to inline-block:
.collapsing-margins {
margin: 100px 0;
background: #f00;
display: inline-block;
width: 100%;
}
<div class="parent">
<div class="collapsing-margins">Element</div>
<div class="collapsing-margins">Element</div>
</div>

Related

Is it possible to over-ride 'overflow: hidden' parents with 'position: sticky'?

I have a two column layout where I want the right column to be position: sticky so it stays in view while scrolling the longer left column.
These are two bootstrap columns, so the first thing I had to do was remove the floats (and instead am using display: inline-block).
This works just fine on its own, or near the top of the DOM of this particular page, but in the rendered location (which, alas, is some 30 or so divs deep...don't ask...) I can't get it to work. Both columns just keep on scrolling.
I know if the parent element has an overflow property other than visible that can break position: sticky but that doesn't seem to be the issue here. Is it that if any parent element up the chain has overflow set that it can break sticky positioning?
I'm just sure what to be looking for in this situation as to determine what is breaking it in this situation. Are there other key things to look out for when it comes to sticky positioning?
EDIT: I reworded my question as it does definitely appear (after further investigation and testing) that the issue is a parent element near the top of the DOM was set to overflow-x: hidden. As this is shared code I'll have to hunt down the reason for that and why it's there.
But...in the interim, is there any known workarounds to this...where one can use an element further down the DOM tree as the containing element for the item positioned sticky?
In the example below, if you remove the overflow from .theproblem the page behaves as I want (right column 'sticks' as you scroll through the page).
.theproblem {
overflow-x: hidden;
}
.column {
display: inline-block;
width: 45%;
vertical-align: top;
}
.column1 {
border: 1px solid red;
height: 1000px;
}
.column2 {
border: 1px solid green;
position: sticky;
top: 1px;
}
<div class="theproblem">
<div class="columnwrapper">
<div class="column column1">
This is column 1 (the tall one)
</div>
<div class="column column2">
This is column 2 (the sticky one)
</div>
</div>
</div>
JSBin link
As you already noticed any overflow property between the sticky position and the scroll will break it (explained here: Why is 'position: sticky' not working with Core UI's Bootstrap CSS and here What are `scrolling boxes`?).
One workaround in your case is to move the scroll to another element and hide the default one:
.theproblem {
overflow-x: hidden;
}
/* Added this */
.columnwrapper {
height:100vh;
overflow:auto;
}
body {
overflow:hidden;
margin:0;
}
/**/
.column {
display: inline-block;
width: 45%;
vertical-align: top;
}
.column1 {
border: 1px solid red;
height: 1000px;
}
.column2 {
border: 1px solid green;
position: sticky;
top: 1px;
}
<div class="theproblem">
<div class="columnwrapper">
<div class="column column1">
This is column 1 (the tall one)
</div>
<div class="column column2">
This is column 2 (the sticky one)
</div>
</div>
</div>

What does collapsing width height and margin mean for block level elements?

What does it mean that the width of block level elements can not be collapsed but the height can?
And can you please explain this text from the W3.org specification:
In CSS, the adjoining margins of two or more boxes (which might or might not be siblings) can combine to form a single margin. Margins that combine this way are said to collapse, and the resulting combined margin is called a collapsed margin.
The meaning of the word collapse is causing much of the confusion here.
A collapsed margin is the name given to the instance when margins of two different elements occupy the same space.
Consider the following example:
.box {
height: 50px;
width: 50px;
}
.box1 {
background: red;
margin-bottom: 25px;
}
.box2 {
background: blue;
margin-top: 50px;
}
<div class="box box1"></div>
<div class="box box2"></div>
It's difficult to tell, but that whitespace between the two boxes is only 50px. You might think it should be 75px, because I've specified a margin-bottom of 25px on the top box, and a margin-top of 50px on the bottom box. 25 + 50 = 75, so why is the whitespace only 50px?
Well, margins can't have any content within them; a margin is specifically denoting a lack of content. Considering there is no content to display in a margin, the parser thinks they might as well be combined to optimise space.
The word 'collapsed' comes about because there are technically two different 'segments' of margins existing in the same place at the same time, 'collapsing' in on each other.
Note that this doesn't happen with margin-left and margin-right:
.box {
height: 50px;
width: 50px;
float: left;
}
.box1 {
background: red;
margin-right: 25px;
}
.box2 {
background: blue;
margin-left: 50px;
}
<div class="box box1"></div>
<div class="box box2"></div>
The space above is indeed 75px. This can be a confusing concept to wrap your head around, but it's important to note that it only affects vertical margins. Further information about collapsing margins can be found at CSS Tricks and Mozilla.
It's also important to note that, by default, a block-level element takes up 100% of the width of its parent, but 0% of the height.
Here's an example illustrating this:
.parent {
background: blue;
border: 10px solid purple;
height: 50px;
width: 200px;
}
.child {
background: red;
}
<div class="parent">
<div class="child">Text</div>
</div>
In the above example, I specify both a width and a height on the parent, though I don't specify either on the child. As you can see, the child element inherits the 200px width, but does not inherit the 50px height.
Hopefully this helps clarify that a bit!

How do I make proper vertical margin between 2 inline blocks?

I have two divs with display:inline block next to each other, however the 2nd one's width can change to be so long that it will fall under the first div. That is fine, but the problem is that there is no vertical space between the 2 divs when this happens. I can solve this by adding margin-bottom to the first div, but then this causes the 2nd div to be a bit lower even when it is sitting to the right of the first div.
What browser are you using? As you can see below, two inline-block divs retain a margin when one slips below the other. (In fact, getting rid of the margin between inline-block elements is a bit tricky, but that's another question.)
#container {
width: 200px;
}
#top {
display: inline-block;
width: 100px;
height: 100px;
background: red;
}
#right {
display: inline-block;
width: 150px;
height: 100px;
background: blue;
}
<div id="container">
<div id="top"></div><div id="right"></div>
</div>
What you want is "vertical-align:top;".

Why margins of such a box containing a in-flow box collapse

Here are my test code:
.div1 {
width: 100px;
background: red;
padding-top: 0.1px;
border: solid;
}
.div2 {
width: 100px;
margin: 100px;
background: pink;
}
.div3 {
overflow: visible;
}
.div4 {
overflow: hidden;
}
<p>sample 1</p>
<div class="div1">
<div class="div2"><div class="div3"></div></div>
</div>
<hr />
<p>sample 2</p>
<div class="div1">
<div class="div2"><div class="div4"></div></div>
</div>
According to the spec:
top and bottom margins of a box that does not establish a new block formatting context and that has zero computed 'min-height', zero or 'auto' computed 'height', and no in-flow children
margins of both div2 may not collapse. However, in sample 2, margins didn't collapse as I thought, but they collapse in sample 1. So why?
According to BoltClock's explanation, margins in sample 1 collapse because the in-flow child(i.e. div3) don't establish a new BFC. I tried another test:
.div1 {
width: 100px;
background: red;
padding-top: 0.1px;
border: solid;
}
.div2 {
width: 100px;
margin: 100px;
background: pink;
}
.div5 {
overflow: visible;
float: left;
/* position: absolute;*/
}
<div class="div1">
<div class="div2"><div class="div5"></div></div>
</div>
With float value other than none, div5 establish a new BFC, so margins of div5 may not collapse accordingly. But they collapse unfortunately.
From the spec:
Note the above rules imply that:
A box's own margins collapse if the 'min-height' property is zero, and it has neither top or bottom borders nor top or bottom padding, and it has a 'height' of either 0 or 'auto', and it does not contain a line box, and all of its in-flow children's margins (if any) collapse.
Emphasis mine.
In sample 2, div4 establishes a new block formatting context. As a result, its own margins are not adjoining (i.e. it does not meet the condition that you have quoted), and so they cannot collapse. Because div4's margins do not collapse, the margins of its own div2 parent cannot collapse through it. As a result, none of the positive margins in sample 2 collapse.
Floating an element takes it out of the flow, so the margins of div2 in your new test are able to collapse because it has no in-flow children.
This is very interesting question. I never faced with this before, but now, according to some articles I found that some situation prevents margin collapsing:
http://www.sitepoint.com/web-foundations/collapsing-margins/
floated elements
absolutely positioned elements
inline-block elements
elements with overflow set to anything other than visible (They do
not collapse margins with their children.)
cleared elements (They do not collapse their top margins with their parent block’s bottom margin.)
the root element
Apparently overflow prevent margin collapsing not only if it is applied on parent element ;)
From other article I saw, that this effect is used in several micro clearfix.

Why does this floating parent calculate it's width before taking sibling into account?

I am trying to understand why the .item-wrap in the css below only calculates it's width *as if .floatleft2 wasn't there, and yet the .items contained by .item-wrap clearly are aware that .floatleft2 is there.
I want the .containingbox to "shrink wrap" the content, but not for the .items to wrap "prematurely" i.e. while there is still extra screen space. (see 'working' fix below).
I have already found the workaround, but what specification in CSS causes this interaction between .floatleft2, .item-wrap, and .item such that the .item-wrap width isn't wide enough to incorporate all the .items?
jsfiddle demo (code reproduced below)
jsfiddle demo with 'working' inline fix
<body>
<h1>float:left on .containingbox, with item-wrap, with floatleft2, causes premature wrapping of .item</h1>
<div class="containingbox">
<div class="floatleft2"></div>
<div class="item-wrap">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
</div>
</body>
body {
margin: 20px;
padding: 0;
font: normal 85% arial, helvetica, sans-serif;
color: #000;
background-color: #fff;
}
.containingbox {
height: 200px;
border: 1px solid #000;
float: left;
}
.item-wrap {
border: 1px solid #0FC;
height: 3px;
}
.item {
border: 1px solid #F09;
width: 50px;
float: left;
position: relative;
margin: 0 10px 0 0;
height: 75px;
}
.item::before { content: "item"; position: absolute; }
.floatleft2 {
height: 75px;
background-color: #000;
border: 1px solid #000;
float: left;
margin: 0 10px;
display: block;
width: 50px;
}
When you float .floatleft2 but not .item-wrap, .floatleft2 is taken out of the normal flow of the container box (which gets its own block formatting context from being floated itself), and .item-wrap is laid out as though .floatleft2 were not there. Since .item-wrap is not floated, it behaves like a regular block-level element, using the auto width and stretching to fit the container as per section 10.3.3.
The reason why the container is sized horizontally to just fit .item-wrap and its floated items is because, when .floatleft2 is taken out of the normal flow of the container, the container no longer needs to account for the size of .floatleft2. It only accounts for the contents of .item-wrap, which are themselves also floated.
The width of a floating element, when no explicit width is specified (it uses the auto width), is shrink-to-fit, according to section 10.3.5. CSS2.1 does not say how to implement shrink-to-fit, but it does say that an implementation should use shrink-to-fit. In this case, the container is shrunk to just the minimum width needed to fit the floating items on one line. The width of .item-wrap is never relevant except that it should stretch to fit within the bounds established by the container, as mentioned above.
What happens then is that when .floatleft2 is introduced, the floating items float to the left of that element (the same fundamental behavior you see when floating the items themselves), regardless of the layout of .item-wrap or the container. This causes some of the items to wrap to the next line since neither container element changes its size to account for .floatleft2.
you are making the div class from block level element to inline element.
you should use float:left; and remove the display:inline
.item-wrap {
/*display: inline;*/
float: left;
border: 1px solid #0FC;
height: 3px;
}
Here is the Working Demo. http://jsbin.com/vicusesu/1/edit
It is because you have float: left on .float-left2 and .item but not on .item-wrap. This effectively removes all floated items from item-wrap (they are floated) while it still keeps the same width as if they were there.
If you add a float (left or right) to your .item-wrap you will not have this issue.

Resources