CSS > 行内格式化上下文中的各类高度计算

前言碎碎语:标题问题在昨天困扰了笔者好久好久,早上把问题提到了各网络也暂时没有回复。由于明天要早起飞异地参加一场校招面试,笔者仍是很紧张的,但奈何问题不解决寝食难安……因此仍是卯起劲从新思考这个问题,算是暂时有了一个本身比较承认以及清晰的答案,与各位读者分享。如您有不一样观点想法意见建议,恳请斧正!html

正式探讨以前,咱们观察一个现象(在 Chrome 下的表现,其余浏览器下的表现和计算可能有细微差异):面试

上图对应的 HTML 是(以后的探讨均基于此):浏览器

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Line Height</title>
<style>
body {
    margin: 0;
    font: 32px/1 'Microsoft YaHei';
}
div {
    width: 400px;
    margin: 30px auto;
    outline: 1px solid black;
    background: #008E59;
}
img {
    height: 80px;
    margin-top: 10px;
}
</style>
</head>
<body>
    <div>
        <span>Some Text</span>
        <img src="picture.jpg" alt=""/>
    </div>
</body>
</html>

咱们来计算下 DIV 和 SPAN 的高度网络

document.getElementsByTagName('div')[0].offsetHeight
//93
document.getElementsByTagName('span')[0].offsetHeight
//42

对于此图,笔者产生以下疑问:测试

  • line-height 为 32px,为什么 SPAN 的高度为 42px?字体

  • DIV 的高度 93px,比 IMG 高度加外边距 90px 以及 SPAN 高度 42px 都要大,如何计算的?网站

  • 图片和文本下的空白(即使没有文本同样存在)是如何产生的?spa

假设咱们把 IMG 删除,HTML 部分改成:设计

<body>
    <div>
        <span>Some Text</span>
    </div>
</body>

此时来计算:代理

document.getElementsByTagName('div')[0].offsetHeight
//32
document.getElementsByTagName('span')[0].offsetHeight
//42

新问题又来了:

  • DIV 的子元素高度为 42px,为什么没有“撑起” DIV 的高度?

以上问题就是本文要探讨的了。覆盖了五个知识点:

  1. 行内盒(或行内不可替换元素)的高度

  2. 行内可替换元素的高度

  3. 行盒的高度

  4. 行距与行高

  5. 创建行内格式化上下文的块盒的 auto 高度

因此在探讨以前,笔者已假设您知道这些概念:行内盒、行内不可替换元素、行内可替换元素、行盒、行内格式化上下文。若是您还有点不清楚,咱们能够快速补习下:

可替换元素、不可替换元素

简单地讲,可替换元素是指须根据其标签和属性来决定具体显示内容的元素,如本文中会探讨的 IMG 元素,其具体显示内容由 src 等属性决定; 不可替换元素则是内容直接呈现的元素。如本文会探讨的 DIV 和 SPAN 等。

块盒

此概念是块格式化上下文的内容,要解释起来就更复杂啦,笔者粗陋地给您一个描述:块盒一般是 display: block 的不可替换元素。

行内级元素、行内级盒、行内盒、行内格式化上下文

display: inline|inline-table|inline-block 产生行内级元素。行内级元素生成行内级盒,而这些盒会参与行内格式化上下文。

display 值是 inline 的不可替换元素会生成一个行内盒。

不是行内盒的行内级盒被称为原子行内级盒。

行盒

在行内格式化上下文中,盒从包含块的顶部一个接一个地水平摆放。包含了一行里全部盒的矩形区域被称为行盒。一个段落就是多个行盒的垂直堆叠。

所以,咱们能够获得下图(大体描摹):

如今开始计算!

1 行内可替换元素和文档流内行内块可替换元素高度计算

W3C 有明确规范,以下:

若是 heightwidth 计算值均为 auto 且该元素有固有高度,那么该固有高度为 height 使用值。

不然,若是 height 计算值为 auto,且该元素有一个固有比例,则 height 的使用值为:

width 使用值 / 固有比例

不然,若是 height 计算值为 auto,且该元素有固有高度,那么该固有高度为 height 使用值。

不然,若是 height 计算值为 auto,但以上状况均不符合,那么 height 的使用值必须设定为一个最大矩形的高度,该矩形比例为2:1,高度不超过150px,且宽度不大于设备宽度。

所以,在咱们的实例中,IMG 盒的高度为 80+10 = 90px。

2 行内盒的高度计算

“高度”一词在这里很有歧义,笔者认为,总共能够有三种概念须要辨析:

  • 行内盒的内容区域高度

  • 行内盒的盒高度

  • 计算行盒高度时的行内盒的盒高度

您可能对第二和第三解释抱有疑问,但咱们先搁置怀疑,把清楚明白的东西先解决。

当咱们用 JavaScript 去获取一个行内盒的 offsetHeight 值时,如咱们上面所作的:

document.getElementsByTagName('span')[0].offsetHeight

笔者将此高度称做“行内盒的盒高度”,类比于咱们所熟知的块盒盒高度。其计算值是:

内容区域高度 + 上下边框 + 上下内边距 = 行内盒的盒高度

边框和内边距的宽度默认为 0,不然为咱们本身指定,但“内容区域高度”是怎么计算的呢?

W3C 这么说:

height 不适用。内容区域的高度应基于字体,但本规范没有指定如何。用户代理可能,好比说,使用行高盒 EM-Box 或字体的最大上端部 Ascender 和下端部 Descender。(后一种会确保有部分在行高盒之上或之下的字符仍然落在内容区域内,但会致使不一样字体有大小不一的盒子;前者则确保做者能够控制相对于 line-height 的背景设计,但也致使字符绘制在其内容区域以外。)

言下之意:

  1. height 属性无效

  2. 行内盒内容区域高度在规范里面没有定义,浏览器能够本身折腾

既然规范没有明确规定计算,咱们让浏览器实测一下。笔者浏览器测试以下:

  • Chrome 42

  • IE11 42

  • Firefox 43

若是咱们更改字体,假设应用以下 CSS

body { font-family: Simsun; }
  • Chrome 33

  • IE11 37

  • Firefox 35

而若是咱们修改 line-height,以上结果均不受影响。

笔者也曾疑惑,这个 offsetHeight 就是内容区域高度吗?答案:是。笔者的验证方法是基于 W3C 以下规定:

尽管不可替换元素的外边距、边框以及内边距不归入行盒的计算,它们仍然渲染在行内盒的周围。这意味着若是 line-height 指定的高度小于被包含盒的内容高度,内边距和边框的背景和颜色可能“流进”毗邻的行盒。用户代理应当按文档顺序渲染这些盒。这会形成后面的盒的边框绘制在前面盒的边框和文本上。

您能够用如下代码实测,会发现红色行内盒的背景溢出到了黑色行内盒所在的行盒。

<div>
    <span style="background:red">Some Text</span>
    <br/>
    <span style="background:rgba(0,0,0,.5)">Some Text</span>
</div>

可知内容区域高度,即行内盒没有内边距和边框时的 offsetHeight

所以总结论是:

行内盒的内容区域高度计算没有统一的标准,不一样的字体或者不一样的浏览器均可能致使不一样的结果,且其高度与 line-height 无关。

由此咱们没法确切地得到一个跨浏览器的行内盒的内容区域高度。一样咱们也没法确切得到一个跨浏览器的行内盒高度(由于其计算式里面就包括了不定变量内容区域高度)。

但问题来了,不一样浏览器都采用不一样的行内盒内容区域高度,又如何能统一计算行盒以及块容器的高度呢?这个问题便致使了笔者在上面所提到的“计算行盒高度时的行内盒的盒高度”概念。

咱们进入下一个话题,行盒高度计算。

3 行盒高度计算

根据规范,行盒的高度决定以下:

  1. 计算行盒内每一个行内级盒的高度。对于可替换元素、行内块元素以及行内表格元素,高度是其外边距盒的高度;对于行内盒,高度是其 line-height

  2. 行内级盒根据其 vertical-align 属性垂直对齐。若是它们对齐 topbottom,它们必须以能最小化行盒高度的方式对齐。若是这些盒足够高,则有多种解决方案而且 CSS2.1 没有规定此行盒的基线的位置。

  3. 行盒高度是最上盒顶部到最下盒底部的距离。

懂了:W3C 尽管容许浏览器有本身的行内盒内容区域计算方式,但统一了一个行盒高度的计算方式:

计算行盒的高度时,针对行内盒,高度直接取 line-height。行内盒能够有边框、内边距、外边距,然而跟行盒的高度彻底不要紧!

根据此规定,咱们很快能够得出,计算行盒高度时,SPAN 盒的高度取 32px。

接着,因为咱们的 vertical-align 是默认的 baseline,所以,应当把盒的基线同父盒的基线对齐。若是盒没有基线,对齐盒的下外边距边缘与父盒的基线。也就是说,把 SPAN 盒的基线同 DIV 盒的基线对齐,把 IMG 盒的下外边距边缘同 DIV 盒的基线对齐。

下图是字体的基线、上下端线等位置信息

图片来源:http://blog.justfont.com/

笔者做图以下:

假设咱们设 DIV 盒的基线是 0,则 IMG 盒的下边缘同 DIV 盒基线对齐;上边缘(上外边距边缘顶部)在高于基线 90px 处。而 SPAN 盒因为其基线对齐 DIV 盒基线,故其行盒下边缘略低于基线。

整个行盒的高度即 IMG 盒上边缘到 SPAN 盒下边缘。假设没有 IMG 元素,则高度为 SPAN 盒的 line-height

但读者您可能注意到了,29 和 -3 是怎么得来的呢?下面,笔者带您算!

4 行距和行高计算

29 和 -3 两值是在计算行距和行高后得来的。咱们先来看规范:

CSS 假设每种字体都由字体特性来指定一个基线之上的特性高度和之下的特性深度。本节中咱们用A表示(给定字体给定字号的)高度,用D表示深度。同时定义 AD = A + D,即从顶部到底部的距离。(参见下面如何找到TrueType和OpenType字体的A和D)注意该字体的这些特性是就整个而言的,无须对应任何个别字符的上端部和下端部。

用户代理必须在一个不可替换行内盒中依照字符的相应基线对齐各个字符。接着,就每一个字符来决定A和D。注意单个元素的字符可能来自于不一样字体所以不见得全部的A和D同样。若是行内盒彻底不包含字符,则被视为包含了一个具备元素首个可用字体的A和D的支柱(一个零宽度的不可见字符)。

接着对每一个字符添加行距L,其中 L = line-height - AD。行距的通常添加到A之上,另外一半添加到D之下,从而赋予字符以及其行距一个基线之上的完整高度 A' = A + L/2,以及完整深度 D' = D+ L/2。

注意。L可能为负。

包含了全部字符以及字符两侧半行距的行内盒的高度正是 line-height

咱们在上述规定中接触到了这些概念:特性高度 A,特性深度 D,顶部到底部距离 AD,完整高度 A',完整深度 D',行距 L。

关于特性值,笔者 Google 到一个网站,推荐读者使用:

http://fontsgeek.com/

不得不吐槽下,国内真的很难找到这样专业精致的字体网站(也多是个人打开方式不对 >_<)。

好,咱们能够得到咱们实例中 Microsoft YaHei 的字体特性了:Dcsender -536;Height 2703。

  • AD 即内容区域高度,在本例中是 42

  • D 即字体下端(基线之下)高度,为 42*(536/2703) = 8

  • L = 32 - 42 = -10

  • 故,D' = 8 + -10/2 = 3

即知行内盒的下边缘在基线之下 3px。同时行内盒的高度被视为 32px,故亦知其上边缘在基线之上 29px 处。

咱们说啦,整个行盒的高度即 IMG 盒上边缘到 SPAN 盒下边缘。因此得行内盒高度为 90 + 3 = 93px。

5 创建行内格式化上下文的块盒的 auto 高度

根据 W3C CSS2.1:10.6.3,该高度是从其上内容边缘到其最后一个行盒的下边缘。只考虑文档流内子盒,绝对定位和浮动子盒应被忽略,相对定位子盒不考虑位移,子盒能够是匿名盒。

在本例中,DIV 盒的行内格式化上下文仅有一个行盒,故其高度取该行盒高度,93px。

相关文章
相关标签/搜索