Baseline shifts for block-level element when no content inside - html

I'm working through a CSS problem at http://www.codecademy.com/courses/web-beginner-en-jNuXw/0/1?curriculum_id=50579fb998b470000202dc8b (actually, just helping a friend learn HTML/CSS) and came across a curious issue. If you erase the content in any of the <p> tags within a <div>, the div shifts upward. For example, delete the word 'Mom' without deleting the <p>. As best as I can figure out, this is because the element is set to vertical-align: baseline and for some reason the baseline is changing. I just can't figure out exactly why it's changing or what is causing it to change.
To be clear, I'm not asking for help to get the div's to align. That's simply a matter of setting their vertical-align to 'top'. I'm just trying to understand how the document flow is calculated. The specific question is: why does the empty div shift upwards?
DEMO: jsFiddle
UPDATE: Here is a new jsFiddle - http://jsfiddle.net/tonicboy/2DtTw/3/ which removes a lot of rules to boil the problem down to a simplified use case. From this, we can see that when a <p> tag has text in it, the baseline of the parent <div> is set at the baseline of the text. When you remove the text, the baseline of the parent <div> is set to the bottom of the <div>. Why is that?
HTML:
<div class="friend" id="best_friend"><p>Arthur</p></div>
<div class="friend"><p>Batmanuel</p></div>
<div class="friend"><p>Captain Liberty</p></div>
<div class="friend"><p>The City</p></div>
<div class="friend"><p>Justice</p></div>
<div class="family"><p></p></div>
<div class="family"><p>Dad</p></div>
<div class="family"><p>Bro</p></div>
<div class="family"><p>Sis</p></div>
<div class="family"><p>Rex</p></div>
<div class="enemy"><p>Baron Violent</p></div>
<div class="enemy"><p>The Breadmaster</p></div>
<div class="enemy"><p>The Deadly Nose</p></div>
<div class="enemy"><p>Dinosaur Neil</p></div>
<div class="enemy" id="archnemesis"><p>Chairface</p></div>
CSS:
div {
position: relative;
display: inline-block;
height: 100px;
width: 100px;
border-radius: 100%;
border: 2px solid black;
margin-left: 5px;
margin-top: 5px;
text-align: center;
}
div p {
position: relative;
margin-top: 40px;
font-size: 12px;
}
.friend {
border: 2px dashed green;
}
.family {
border: 2px dashed blue;
}
.enemy {
border: 2px dashed red;
}
#best_friend {
border: 4px solid #00C957;
}
#archnemesis {
border: 4px solid #cc0000;
}

I think I've mostly figured out the reason, after digging through W3C specs. Here are three key items from the spec which may explain this behavior:
"Line boxes that contain no text, no preserved white space, no inline elements with non-zero margins, padding, or borders, and no other in-flow content (such as images, inline blocks or inline tables), and do not end with a preserved newline must be treated as zero-height line boxes for the purposes of determining the positions of any elements inside of them, and must be treated as not existing for any other purpose."
When you delete the text, the <p> element is no longer in-flow.
"The baseline of an 'inline-block' is the baseline of its last line box in the normal flow, unless it has either no in-flow line boxes or if its 'overflow' property has a computed value other than 'visible', in which case the baseline is the bottom margin edge."
Because there are no in-flow elements within the parent div, the baseline becomes the bottom margin.
Because the div's are set to display: inline-block, their default vertical alignment is 'baseline'
Because the other div's have in-flow elements (the <p> tags), their baseline is set to the text baseline.
And that is why the empty box's bottom margin aligns with the baseline of the <p> tags in the other div's.

The baseline of the element is shifting because the text inside the <p> determs the baseline height:
In an inline formatting context, boxes are laid out horizontally, one
after the other, beginning at the top of a containing block.
Horizontal margins, borders, and padding are respected between these
boxes. The boxes may be aligned vertically in different ways: their
bottoms or tops may be aligned, or the baselines of text within them
may be aligned.
source: http://www.w3.org/TR/CSS2/visuren.html#block-formatting
The height of each inline-level box in the line box is calculated. For
replaced elements, inline-block elements, and inline-table elements,
this is the height of their margin box; for inline boxes, this is
their 'line-height'.
source: http://www.w3.org/TR/CSS2/visudet.html#line-height
CSS assumes that every font has font metrics that specify a
characteristic height above the baseline and a depth below it. In this
section we use A to mean that height (for a given font at a given
size) and D the depth. We also define AD = A + D, the distance from
the top to the bottom.
source: http://www.w3.org/TR/CSS2/visudet.html#inline-box-height
So with this block being a inline-block and baseline is calculted based on the line-height which is calcuted by different font types. Because this <p> has no font/text the baseline will not be positioned.
place all the line-height: 0; and you will see that the one with no text/font doesn't react like the other does:
jsFiddle
So why are the other two elemets shifting that have text in them?
Well it's because the text excist of two lines of text. The margin of the text is bigger and uses more space, thus the baseline is pushed further

Related

Understanding CSS2.1 specification regarding height on inline-level boxes

CSS2.1 Section 10.6.1 specifies:
The height of the content area should be based on the font, but this specification does not specify how. A UA may, e.g., use the em-box or the maximum ascender and descender of the font.
The vertical padding, border and margin of an inline, non-replaced box start at the top and bottom of the content area, and has nothing to do with the 'line-height'.
Then, the statements that seem contradictory follow in 10.8 Line height calculations:
The height of a line box is determined as follows:
The height of each inline-level box in the line box is calculated. For inline boxes, this is their 'line-height'....
And:
User agent must align glyphs in a non-replaced inline box to each other by their relevant baselines....
The height of the inline box encloses all glyphs and their half-leading on each side and is thus exactly 'line-height'
There is something I am not fully understanding here.
Is the height of inline-level boxes equal to the line-height property set on them (with the minimum being the line-height set on the parent block container element), OR is it just determined by the font height (and UA implementation)?
EDIT
Just to avoid confusion (since there are many posts about this):
I know that an inline-level element's content height is equal to the font it contains (and UA implementation)
I realize the spec does state that in 10.6.1, but in 10.8.1 it states that the height of an inline-level box is equal to its line-height
That seems contradictory to me
My purpose with this post is to discuss the wording in the spec, in order to fully understand what it means and how it ties in with reality
EDIT:
To avoid the "opinion based" close votes, I have changed the title. It is not my opinion that the spec is contradictory, and I am not asking for anyone else's opinion on whether it is contradictory either.
I realize it is not actually contradictory, I was just trying to understand why it is not, given its wording.
Is the height of inline-level boxes equal to the line-height property set on them (with the minimum being the line-height set on the parent block container element),
Yes it is.
OR is it just determined by the font height (and UA implementation)?
No it isn't
CSS is really about boxes, not elements, and you should try not to confuse the two.
So an inline element has associated with it a number of boxes. A content box, padding box, border box and margin box. It also has zero (if display:none), one, or multiple inline boxes. The content box, paddings, borders and margins may be divided among the inline boxes so that the inline content can be spread over more than one line.
The inline box's height is the content height adjusted by the leading. It's the leading that does the magic here. The leading is defined as the line-height of the element minus the content height of that inline box.
Simply rearranging that equation tells us that the height of the inline box depends only on the line-height and not on the content box (or padding, border, margin boxes).
Note that none of the above discusses the line box, which is a different thing again and not a direct property of inline elements or their boxes. The line box is constructed by arranging the inline boxes that occur on the same line such that their vertical alignments fit the rules computed for the elements, including the zero width inline box formed by the strut.
Each line box is bounded by the top of uppermost inline box and the bottom of the lowestmost inline box that that line box contains.
Digression: On why the height of the line box can surprise.
Suppose we have a simple case of a containing block which just contains one short inline element (i.e. short enough that it fits in a single line box). Also suppose that everything is aligned on the baseline. Let's say that the line-height is set on the containing box to 20px, and the inline element inherits that. Also suppose that the font-size (em-square) of the containing block is 16px, and that means that the font metrics compute to an ascent (above the baseline) of 14px and a descent (below the baseline) of 4px.
So the content area for the strut is (14px + 4px =) 18px high. The line-height is 20px, so there is 2px leading, 1px goes above the ascent, and 1px below the descent. So the line-height of the strut is made of 15px above the baseline and 5px below the baseline.
Now, suppose that the font-size of the inline element is set to 0.5em (i.e. half that of the containing block). The content area for the inline element will be an ascent of 7px and a descent of 2px. The line-height is still 20px, so the leading is 20px - (7px + 2px) = 11px, meaning that 5.5px goes above the ascent and 5.5px goes below the descent. This results in the line-height for the inline element is made of 12.5px above the baseline and 7.5px below the baseline.
Since the strut and the inline element are aligned vertically to their baselines, the top of the uppermost inline box (the strut) is 15px above the baseline and the bottom of the the lowermost inline box (the inline element) is 7.5px below the baseline, the actual height of the line box is not 20px but (15px + 7.5px =) 22.5px.
As I explained in this previous question (Why is there space between line boxes, not due to half leading?) we have the content area and the line box.
The content area is defined by the font properties and the UA (like described in your first quote) and the line box is defined by the line-height (like described in your second quote). Here is some example to show different height of content area based on different fonts and to illustrate the difference with the line box:
span {
border-left: 1em solid red;
background: rgba(255, 0, 0, 0.2);
}
.container>div {
margin-bottom: 5px;
border: 1px solid;
line-height: 30px;
}
.container>div:nth-of-type(1) span{
font-family: arial;
font-size: 25px;
}
.container>div:nth-of-type(2) span{
font-family: "open sans";
font-size: 20px;
}
.container>div:nth-of-type(3) span{
font-family: monospace;
font-size: 30px;
}
.container>div:nth-of-type(4) span{
font-family: cursive;
font-size: 22px;
}
<div class="container">
<div>
<span>Hello World</span>
</div>
<div>
<span>Hello World</span>
</div>
<div>
<span>Hello World</span>
</div>
<div>
<span>Hello World</span>
</div>
</div>
As you can see the background and border apply to the content area that is defined by the font-family and font-size. For all the cases, I set the line-height to be equal 30px thus all the line boxes are equal.
We can also notice that the border-left behave differently which indicates that indeed the height of the content area depends on the font and UA.
Here is the same example with some vertical padding/margin/border in order to illustrate how they affect the content area and has nothing to do with line-height:
span {
border-left: 1em solid red;
background: rgba(255, 0, 0, 0.2);
font-family: arial;
font-size: 25px;
}
.container>div {
margin-bottom: 5px;
border: 1px solid;
line-height: 40px;
}
.container>div:nth-of-type(1) span {
padding: 10px;
}
.container>div:nth-of-type(2) span {
border-bottom: 5px solid red;
}
.container>div:nth-of-type(3) span {
padding: 10px;
background-clip: content-box;/*I limit the background to content-box only*/
}
.container>div:nth-of-type(4) span {
margin: 20px;
}
<div class="container">
<div>
<span>Hello World</span>
</div>
<div>
<span>Hello World</span>
</div>
<div>
<span>Hello World</span>
</div>
<div>
<span>Hello World</span>
</div>
</div>

Understand inline-element, vertical-align, line-box and line-height

vertical-align:bottom, means the bottom inline-box matches the bottom of its line-box, so in my case, the inline-box of span2 is the green one, whose line-height is 100px, inherited from its parent. Its line-box is the black one, also has line-height:100px. so they are bottom aligned.
see pic:
I've already learned that:
1.vertical-align works only for inline/inline-block element
2.vertical-align is based on line-height, not the height of its container!
3.in a line-box, its line-height is the line-height of the inline box(in my opinion, its either inline-element or inline-block element) which has the highest line-height. like pic:
Everything works fine on inline-block element, but it seems that there is a problem with inline-element.
explanation:
parent: height:200px, line-height;100px;
div.child:inline-block, vertical-align bottom;
span1: inline, line-height:inherit from parent, which is 100px
span2: inline, line-height:inherit from parent, which is 100px, vertical align: bottom.
To me, the line-box for the code below is like this(you can run the code first):
strange behavior for span2 !!! and it becomes even stranger if I set its vertical-align to text-top or text-bottom
another thing which I found interesting is, if I set display of span to inline block or set the line-height of span to normal (which is 1.16 of its font-size), everything works fine.
can someone explain it? Thanks
div.parent {
width: 300px;
background-color: coral;
/*key-part*/
height: 200px;
line-height: 100px;
}
div.child {
width: 50px;
height: 50px;
background-color: yellow;
/*key-part*/
display: inline-block;
vertical-align: bottom;
line-height: normal;
}
.span1 {
background-color: white;
font-size: 50px;
/*key-part*/
vertical-align: middle;
}
.span2 {
background-color: green;
font-size: 12px;
/*key-part*/
vertical-align: top;
}
<body>
<div class="parent">
<div class="child">inline-block div</div>
<span class="span1">Text1</span>
<span class="span2">Text2</span>
</div>
</body>
Let's try to cover it step by step:
1.vertical-align works only for inline/inline-block element
Vertical-align applies to inline-level elements. That's currently: inline, inline-block, inline-table, inline-flex, inline-grid. Vertical-align is also used, but in a different way, for table cells.
2.vertical-align is based on line-height, not the height of its container!
Except for table cells, correct.
3.in a line-box, its line-height is the line-height of the inline box(in my opinion, its either inline-element or inline-block element) which has the highest line-height.
That's correct for simple cases but not for complex alignment ones. A better approximation goes something like this. Remove all the elements that are vertical-align:top and vertical-align:bottom. Align all the other elements so that their vertical alignment lines are level with one another. Call the box that contains them from the highest top of the aligned elements to the lowest bottom of the aligned elements the proto-line box. The actual height of the line box is then the maximum of the height of the proto-line box and all of the heights of the elements that are aligned top and bottom.
Now the relevant part of the specification for your question is this:
... for inline non-replaced elements, the box used for alignment is the box whose height is the 'line-height' (containing the box's glyphs and the half-leading on each side, see above). For all other elements, the box used for alignment is the margin box.
So for the span2, the green background area is the box's glyphs and above that are each glyph's upper half-leading, a value which is taken from the 100px line-height inherited from the container block element. It's the top of these half-leadings that aligns with the top of the line-box, not the top of the green background area.
On the other hand, the inline-block div aligns to the bottom of the line box, by the bottom of its bottom margin, and not by any half-leading.

Do block elements ignore floating elements?

W3C states that:
Since a float is not in the flow, non-positioned block boxes created before and after the float box flow vertically as if the float did not exist. However, the current and subsequent line boxes created next to the float are shortened as necessary to make room for the margin box of the float.
This work as expected with divs:
#a
{
width: 100px;
height: 100px;
background-color: blue;
float: left;
}
#b
{
width: 200px;
height: 200px;
display: block;
border: 1px solid black;
background-color: red;
}
<body>
<div id=a></div>
<div id=b></div>
</body>
Here the red div is block-level element, therefore it's ignoring the floating one. (if I changed red one to display: inline-block it would appear next to floating one)
But, if I apply display: block to an image ,it won't ignore the floating div, Why?
#a
{
width: 100px;
height: 100px;
background-color: blue;
float: left;
}
#b
{
width: 200px;
height: 200px;
display: block;
border: 1px solid black;
}
<body>
<div id=a></div>
<img id=b src="https://www.gravatar.com/avatar/5cb44dcd4ebddfea3ede8c6d46b02795?s=328&d=identicon&r=PG&f=1">
</body>
Several paragraphs after the one you quote, the spec says:
The border box of a table, a block-level replaced element, or an element in the normal flow that establishes a new block formatting context (such as an element with 'overflow' other than 'visible') must not overlap the margin box of any floats in the same block formatting context as the element itself.
Although you've applied display: block to your image, being an image it's a replaced element, and therefore the float cannot intrude into its bounds.
Only non-replaced block boxes that don't establish block formatting contexts and are in the same flow as a float may ignore the float.1 A block-level replaced element is not a block box, because a replaced element cannot be a block container box.2
1 You're probably thinking, that's a ridiculously specific statement, and it is. It's why terms like "block element" are considered extremely vague in CSS parlance. Then again, it doesn't help that CSS itself defines almost equally vague terms like "block box" to specifically refer to boxes that are both block-level boxes and block container boxes.
2 This does imply that "non-replaced block box" is somewhat of a tautology, which reinforces just how ridiculously specific that statement is.
W3C
These are the two things that stuck out to me when viewing the W3C. It's considering it as a line box.
A floated box is shifted to the left or right until its outer edge touches the containing block edge or the outer edge of another float. If there is a line box, the outer top of the floated box is aligned with the top of the current line box.
A line box is next to a float when there exists a vertical position that satisfies all of these four conditions: (a) at or below the top of the line box, (b) at or above the bottom of the line box, (c) below the top margin edge of the float, and (d) above the bottom margin edge of the float.

Why does x-overflow:hidden cause extra space below?

I have two spans inside each other. On the inner span I have overflow-x:hidden. This causes extra space below the inner span. Why?
<span style="" class="yavbc-color-tip"><span style="">Some text</span></span>
Fiddle: http://jsfiddle.net/U92ue/
Note: I have only tested in latest Chrome.
Visual formatting model - 9.4.1 Block formatting contexts
Floats, absolutely positioned elements, block containers (such as inline-blocks, table-cells, and table-captions) that are not block boxes, and block boxes with 'overflow' other than 'visible' (except when that value has been propagated to the viewport) establish new block formatting contexts for their contents.
More specifically, a new block formatting context is established when the overflow property is changed. By default, an element's vertical-align property value is baseline. You can simply change this to something like top in order to fix this.
Updated Example
span.yavbc-color-tip span {
display: inline-block;
padding: 3px;
border-radius: 8px;
border: none;
background-color:#005e8e;
color:#7cd3ff;
overflow-x: hidden; /* This gives extra space under this span. Why? */
vertical-align:top;
}
Notice this doesn't happen when the element's display isn't changed to inline-block? It doesn't occur with inline elements - example demonstrating this.
Despite the above quote in the accepted answer, this behavior has nothing to do with Block Formatting Context and with "block" part of the inline-block value at all. It's all about the "inline" part of it.
All inline-* elements participate in the Inline Formatting Context. That means, they are placed inside the so called "line boxes" along with text and other inline-level elements. These elements and text are aligned with each other, so the height of each line box is calculated from the top of the highest element to the bottom of the lowest one.
By default, inline-level elements are aligned with the baseline of their fonts (see first line in the example below). Even if parent element has no actual text, the position of the baseline and the minimal height of the line box are calculated as if it had text (the spec calls this "imaginary" text the "strut" of the element). That's why the line box always has some space above the baseline (for the font ascenders and diacritics) and below it (for the font descenders) — see the second line of the example.
The tricky part for the inline-block elements is that overflow property changes what is considered the baseline for these elements (end of section 10.8.1 of the spec):
The baseline of an 'inline-block' is the baseline of its last line box in the normal flow, unless it has either no in-flow line boxes or if its 'overflow' property has a computed value other than 'visible', in which case the baseline is the bottom margin edge.
So, while technically the space below the baseline is always reserved, with default overflow: visible, the inline-block element is placed so that its text's baseline is aligned with the parent baseline, and its bottom part is moved below it. In many cases this makes this inline-block the lowest element in the line, so the space reserved for font descenders is not visible. However, changing the overflow value makes the whole element render above the baseline (like an <img> element), making all this space visible.
p {
margin: .5em;
font: 32px/1.5 serif;
color: #fff;
background: #888;
}
span {
display: inline-block;
font: 12px/1 sans-serif;
background: #fff;
color: #000;
padding: 2px;
border: 1px solid green;
}
.ovh {
overflow: hidden;
border-color: red;
}
<p>Text with image <img src="http://via.placeholder.com/30x15"> and <span>two</span> <span>inline-block</span>s</p>
<p><img src="http://via.placeholder.com/30x15"> <span>only</span> <span>inline-blocks</span></p>
<p><img src="http://via.placeholder.com/30x15"> <span>regular</span>, the <span class="ovh">overflowed</span> trick</p>
In general, inline formatting is tricky. You can find a good explanation of some of its gotchas and surprises in this article: http://iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align.
I'd say that a good rule of thumb would be not using display: inline-* for its side effects, if you aren't really going to place the element inside the text. In the OPs example, the best solution would be to use display: block for the inner span, that doesn't involve any "magic" like line box calculation.

Align div that are inline-block

Can anyone explain the behaviour of the divs here http://jsfiddle.net/Z7z5Q/ ? I wonder, why they are not aligned on one line ? And why adding text to div moves other divs ?
Here is html and css:
<!DOCTYPE html>
<html>
<head>
<link type="text/css" rel="stylesheet" href="stylesheet.css"/>
<title>My Social Network</title>
</head>
<body>
<!--Add your HTML below!-->
<div class="friend">Some text in more than two line, actually in 3</div>
<div class="family">Some text in two line</div>
<div class="friend" id="best_friend"></div>
<div class="enemy" id="archnemesis"></div>
<div class="friend">Some text</div>
<div class="family"></div>
<div class="friend"></div>
<div class="family"></div>
</body>
</html>
Css:
div {
display: inline-block;
margin-left: 5px;
height:100px;
width:100px;
border-radius: 100%;
border: 2px solid black;
}
.friend {
border: 2px dashed #008000;
}
.family {
border: 2px dashed #0000FF;
}
.enemy {
border: 2px dashed #FF0000;
}
#best_friend {
border:4px solid #00C957;
}
#archnemesis {
border: 4px solid #CC0000;
}
Thanks. Will appreciate links to docs or articles.
The elements are aligned... but not in the way you intended it, obviously ;)
The key to your problem is the property vertical-align.
First remove border-radius in order to better see the boxes.
Then add vertical-align: middle;: problem solved (see fiddle)
Content or not, each box is now aligned relatively to its fixed height (you fixed it to 100px).
What happened in the first place without vertical-align: middle;? Change the value for baseline: you're back to the original problem. This is the default value, the one you do want when displaying text in spans for example or a label and the text of a text field, whatever the padding and margin on both. But with text forced to occupy 2 or 3 lines (boxes are 100px wide whatever their content), the baseline is very different from what you'd expect, it's the baseline of the content aka the last line of text.
Same with empty div: as they lack content to align with, their baseline is their bottom side (not so sure about this one, but that's what seems to happen).
Add a single character or a non-breakable space in some empty div: their baseline is now completely different from an empty div. See other fiddle
The same phenomenon happens with a tall image lost in a paragraph of text; you can align it with vertical-align but the difference is that it's easier to see what's happening. Here you haven't a single occurence of "normal" text so spotting it is a bit harder.
Why float: left; does work here? It'll take each box, all of the same height, and align it relative to the box, not to its content. But then you've to manage clearing and 1px more on a box can break the alignment of all the following boxes...
inline-block is an attribute with a number of curiosities. In this example, you can plainly see that removing height: 100px from the div CSS rules will result in the elements having their text aligned, which isn't so obvious with the flashy circle-shaped dashed multicolored borders. So to fix this, you would apply vertical-align: top. (source)
The baseline of an 'inline-block' is the baseline of its last line box in the normal flow, unless it has either no in-flow line boxes or if its 'overflow' property has a computed value other than 'visible', in which case the baseline is the bottom margin edge.
(from a great answer from another thread)
In comparison, floats align at the top by default.
align on one line
div {vertical-align: middle;}
Adding a float:left; will solve this problem see here: http://jsfiddle.net/Z7z5Q/5/
Also adding vertical-align:top; will solve it as well: http://jsfiddle.net/Z7z5Q/7/
This is because inline-block behaves weird with white spaces in html.