Why baseline of `inline-block` element with `overflow:hidden` is set to its bottom margin?

夙愿已清 提交于 2019-11-29 01:25:33

1. What the reason to change baseline of inline-block element from baseline of its line box to bottom margin edge?

The baseline of an 'inline-block' is changed to its bottom margin edge when its overflow property is set to hidden (full specification here).

As for the reason for this decision, I think since the overflown part is hidden, user agents (browsers) may choose to render that overflown part and not display it, or choose to not render it at all. And when the overflown part is not rendered, user agents have no way to tell the baseline of its last line box, as it is not rendered, where it goes is not known.

If the baseline of 'inline-block' whose overflow is set to hidden is still kept as the baseline of its last line box, user agents are forced to render what is hidden to user, which may hinder performance, or at least, put extra restrictions on user agents. What's more, in such case, other inline texts in the same line box are aligned to such a baseline where texts around the overflow-hidden inline-box is hidden, which is kind of stange and not intuitive.

I made a simple demo emulating that inline-block with overflow hidden still has its baseline set to the baseline of its last line box.

var isOverflowHidden = false;
document.querySelector('button').onclick = function() {
  document.getElementById('inline-box').style.overflow = isOverflowHidden ? '' : 'hidden';
  isOverflowHidden = !isOverflowHidden;
}
html { background: white; }
#inline-box { display: inline-block; height: 18px; }
.overflown { color: white; }
<p><button id="toggle">Toggle 'overflow: hidden;' on 'inline-block'</button></p>

<span>
  texts sit
  <span id="inline-box">
    texts in inline-block <br>
    <span class="overflown">
      line 2 <br>
      line 3
    </span>
  </span>
  on baseline
</span>

Besides, you may also compare this behavior with display: none. When that's set, clientWidth and clientHeight both equates to 0.

2. How to calculate this shift?

This part is much easier, since it's documented in the link you gave in the question.

I'll start from the definition of 'line-height'.

The height of the inline box encloses all glyphs and their half-leading on each side and is thus exactly 'line-height'.

That is, line-height is composed of, from top to bottom, top half-leading + height(ascent) + depth(descent) + bottom half-leading.

Height of each component can be calculated for a given font at a given size.

Basically, every font has font metrics that specify a characteristic height above the baseline and a depth below it.

Take 'Times New Roman' as an example, using FontForge, we see that it has Em Size as 2048, HHead Ascent as 1825, and HHead Descent as -443. That is, 1825 / 2048 = 89.1% of font-size contributes to the ascent, and 443 / 2048 = 21.6% contributes to the descent.

There are also metrics starting with 'Typo', that category will be used if 'Really use Typo metrics' is checked, and the spec recommends this:

Note. It is recommended that implementations that use OpenType or TrueType fonts use the metrics "sTypoAscender" and "sTypoDescender" from the font's OS/2 table for A and D (after scaling to the current element's font size). In the absence of these metrics, the "Ascent" and "Descent" metrics from the HHEA table should be used.

Line-height minus ascent and descent is the so-called leading.

Half the leading is added above A(ascent) and the other half below D(descent).

Assuming a font-family: Times New Roman; font-size: 100px; line-height: 200px;, we get

ascent = 100px * (1825 / 2048) = 89px
descent = 100px * (443 / 2048) = 22px
top half-leading = bottom half-leading = (200px - 89px - 22px) / 2 = 44.5px

So we see that this can be calculated. And this can also be measured on pages.

Here is another demo for you to fiddle with.

If you are asking for the shift of bottom half-leading, it's shown as space between green line and blue line in the code snippet. If you are asking for the shift of descent and bottom half-leading, it's shown as space between red line and blue line in the code snippet.

var $ = document.querySelector.bind(document);

var fontFamily = window.getComputedStyle($('#examinee'))['font-family']
  , fontSize = +window.getComputedStyle($('#examinee'))['font-size'].replace('px', '')
  , containerLineHeight = +window.getComputedStyle($('#examinee'))['line-height'].replace('px', '')
  , textLineHeight = $('.target').offsetHeight
  , ascent = $('#examinee .baseline').offsetTop + $('#examinee .baseline').offsetHeight - $('#examinee .text-top').offsetTop
  , descent = $('#examinee .text-bottom').offsetTop - $('#examinee .baseline').offsetTop
  , topHalfLeading = $('#examinee .text-top').offsetTop
  , bottomHalfLeading = $('#examinee').offsetHeight - 2/* borders of the container */ - $('#examinee .text-bottom').offsetTop - $('#examinee .text-bottom').offsetHeight;

$('#font-family').innerText = fontFamily;
$('#font-size').innerText = fontSize + 'px';
$('#container-line-height').innerText = containerLineHeight + 'px';
$('#text-line-height').innerText = textLineHeight + 'px';
$('#ascent').innerText = ascent + 'px';
$('#descent').innerText = descent + 'px';
$('#top-half-leading').innerText = topHalfLeading + 'px';
$('#bottom-half-leading').innerText = bottomHalfLeading + 'px';
div {
  font-size: 20px;
  line-height: 2;
  width: 650px;
  
  border: 1px dashed gray;
  border-top: 1px solid blue;
  border-bottom: 1px solid blue;
  margin: 1rem 0;
  overflow: hidden;
  white-space: nowrap;
}

span:not([class]) {
  display: inline-block;
  border: 1px dashed gray;
}

.baseline,
.text-bottom,
.text-top {
  display: inline-block;
  width: 200%;
  margin: 0 -100%;
}

.baseline {
  border-bottom: 1px solid red;
  vertical-align: baseline;  /* the default */
}

.text-bottom {
  border-bottom: 1px solid green;
  vertical-align: text-bottom;
}

.text-top {
  border-bottom: 1px solid green;
  vertical-align: text-top;
}

#examinee {
  position: relative;
  font-size: 100px;
  line-height: 200px;
}
<p>
  Demonstrates that "overflow: hidden;" sets baseline of an inline-block element to its bottom margin.
</p>
<div>
  <span class="baseline"></span>
  <span class="text-top"></span>
  <span class="text-bottom"></span>
  &lt;div&gt;
  <span>
    &lt;span style=""&gt;&lt;/span&gt;
  </span>
  &lt;/div&gt;
</div>
<div>
  <span class="baseline"></span>
  <span class="text-top"></span>
  <span class="text-bottom"></span>
  &lt;div&gt;
  <span style="overflow: hidden;">
    &lt;span style="overflow: hidden;"&gt;&lt;/span&gt;
  </span>
  &lt;/div&gt;
</div>

<p>
  Demonstrates the position of baseline, text-top and text-bottom. <br>
  Demonstrates how "line-height" affects box sizing.
</p>

<ul>
  <li>Blue lines: top and bottom borders of line boxes
  <li>Red lines: baseline of texts
  <li>Green lines: text-top or text-bottom of texts
</ul>

<ul>
  <li>Between blue lines: the line-height
  <li>Between red line and green line: ascent or descent
</ul>

<div id="examinee">
  <span class="target">GgJjPpQqYy</span>
  <span class="baseline"></span>
  <span class="text-top"></span>
  <span class="text-bottom"></span>
</div>


Measured metrics:
<ul>
  <li>font-family: <span id="font-family"></span></li>
  <li>font-size: <span id="font-size"></span></li>
  <li>container line-height: <span id="container-line-height"></span></li>
  <li>text line-height: <span id="text-line-height"></span></li>
  <li>ascent: <span id="ascent"></span></li>
  <li>descent: <span id="descent"></span></li>
  <li>top half-leading: <span id="top-half-leading"></span></li>
  <li>bottom half-leading: <span id="bottom-half-leading"></span></li>
</ul>

This might not be the answer. but it might help to resolve this issue by removing the extra space in between inline-block elements.

<style>
.main_div {
    display:table;
    border-collapse:collapse;
    width:100%;
    border:1px solid red;
}
.main_div span {
    display:table-cell;
    border:1px solid black;
    height:20px;
    border:1px solid green;
}

</style>
<div class="main_div">
    <span class="one">one</span>
    <span class="two">two</span>
    <span class="three">three</span>
</div>

@timur: First of all nice question.

I do not know whether it would answer your question but I would like to talk about some behaviour aspect of "inline-block" elements.

First of all, "inline-block" elements act according to its sibling elements and its content.

If there are two divs, one beside the other and both have display:inline-block; property:value then it would depend on the contents inside each div and would start displaying the content from the baseline which is the natural behaviour of "inline-block element".

Now, let me explain you "overflow" property behaviour.

By default, overflow property is "Visible" and its depended property is "overflow-wrap: normal;". Also it gets applied to only "block" level and "inline-block" elements because inline elements are those elements which wrap to the text and there are no white space inside the content to stop overflowing.

So the span in the example you have provided has to be "block" OR "inline-block" to apply overflow and vertical-align.

IF you look at this fiddle ---> http://jsfiddle.net/Lkyd1kr0/1/ where I have used "inline-block" for the second span element.

HTML

<div class="one"><span>as</span></div><div class="two"><span>asd</span></div>

CSS

.one,.two { 
width: 200px;
display: inline-block ;

}

.one { border: 2px solid #f00; }
.two { border: 2px solid #000; }

.one span { display: block; }

.two span { 
    display: inline-block;
    overflow:hidden;
}

Now, just use Web developer tools and hover over the div.two and div.two > span and check the difference in height.

This is due to, as span is the content of the div.two who is inline-block and the rest of the space on the height of the div.two is secured for the rest of the content which is white space. This behavior you would generally see on "block" level and "inline-block" level elements.

Also, notice "transform-origin" of both "span" and "div.two" it would have 4px of difference on y-axis.

It's not will looks like answer, but I found a very interesting thing on this example.

If we take a lool to #firstDiv we can see some - margin in the bottom. But I think this margin taken from the height of horizontal scrollbar.

I take a height of it, the height is near 14px;

Then take a height of top margin of out inline-block with overflow: hidden it's a 15px, and without overflow: hidden it's near 30px.

Is it a coincidence? I do not think. The same trick will be with larger height of #container.

P.S. This response does not purport to answer, I just could not put it all in a comment. I do not know how it's all connected, I just noticed this. Thank you for understanding.

1. What the reason to change baseline of inline-block element from baseline of its line box to bottom margin edge?

Its the default line-height or the normal line-height of its parent inherited to the inline-block element or the default value defined by the user agent which is by the way W3C recommended line-height:normal.

Reference here

http://www.w3.org/TR/CSS2/visudet.html#propdef-line-height says

normal

Tells user agents to set the used value to a "reasonable" value based on the font of the element. The value has the same meaning as .

We recommend a used value for 'normal' between 1.0 to 1.2. The computed value is 'normal'.

So we know the default is line-height:normal and it inherits. Now we know that inline-block adds the element the line-height and lets see what happens when overflow:hidden is set

W3C - Overflow says

  • hidden

The content is clipped and no scrollbars are provided.

overflow:hidden and display:inline-block creates space for the hanging characters like pgy. as it is block and inline, overflow preserves both space for the hanging characters that might come and line-height.

2. How to calculate this shift?

its browser specific and so we would need to refer browser specification

See here to know the value of 'normal'

mozilla

Initial value normal
Inherited yes

normal Depends on the user agent. Desktop browsers (including Firefox) use a default value of roughly 1.2, depending on the element's font-family.

To understand the image, See the HTML CSS below

The Second div and Second Div Span has line-height:normal (which is 1.2 normally) because normal is inherited as its not defined.

The Third div and Third Div Span has line-height:40px which is defined, The value of the third div line-height was by default inherited to its inline child elements too.

CSS/Properties/display says

  • inline-block Causes an element to generate an inline-level block container. The inside of an inline-block is formatted as a block box, and the element itself is formatted as an atomic inline-level box.

As the term inline says it means the element is an inline element, and the block means its block. so together it means its a block element defined in the same line here is the example proof that shows three situations By default DIV is a block-Level element, and SPAN is an Inline Element. More information here HTML Block and Inline Elements

<style>
.first-div{
    border:#F00 1px solid;
}
.first-div span{
    /**by default span is an inline element.**/
    border:#093 1px solid;
}
.second-div{
    /** line-height not defined default line height will be used **/
    border:#F00 1px solid;
}
.second-div span{
    /** default line height inherited from the secod-div **/
    display:inline-block;
    border:#093 1px solid;
    overflow:hidden;
}
.third-div{
    border:#F00 1px solid;
    /**see line height. we change the default line height**/
    line-height:40px;
}
.third-div span{
    /**see line height. line height 40px set in the parent will be used**/
    display:inline-block;
    border:#093 1px solid;
    overflow:hidden;
}
</style>
<div class="first-div">
First Div
<span>First Span</span>
</div>
<br/>
<div class="second-div">
Second Div
<span>Second Span</span>
</div>
<br/>
<div class="third-div">
Third Div
<span>Third Span</span>
</div>
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!