[聚合文章] 浅析行内元素视觉格式化

CSS 2017-11-29 19 阅读

起因

前段时间组内同学在开发运营页的过程中,遇到一个有趣的问题:移动端页面采用 rem自适应布局 ,图片在垂直方向展示时会出现莫名的间隙(如下图所示)。几位同学纷纷提出不同的解决方案,但为什么会出现这种问题呢?秉着知其然且知其所以然,借此行文一篇,大致梳理行内元素视觉格式化方面理论。

前言

上面的问题其实三言两语就可以说个大概,不过扩散到相关知识点却不能简单的一语带过。看过《CSS权威指南》的童鞋会知道,书中第七章有专门讲视觉格式化方面理论。同时 CSS 规范在第九、十章也分别有阐述用户代理如何处理视觉格式化。相比块级元素,行内元素的视觉显示基本术语多也难理解。

块级元素生成块级框,通常不允许其他内容与这些框并存。在充分理解盒模型( BoxModel )后,水平和垂直方向格式化表现很容易预测。行内块级元素可以说是行内元素和块级元素的混合物,实际上行内块级元素表现与行内替换元素相似。

行内元素的视觉显示则涉及较多术语,例如行内替换元素、行内非替换元素、内容区、行内框、行框、字体大小、基线、 lin-heightvertical-align 等,同时也会产生很多意思的问题。

注:后文讨论主要为常规流( normal flow )中的行内元素( inlineelements )布局,不包括浮动( float )、定位( position )元素。后文描述 box 为“框”,也可为“盒”,相同意思不同翻译。

快速复习

  • 常规流( normal flow ):文档文本按照从左向右、从上向下顺序显示(自右而左语言相反)。脱离常规流可以采用浮动或定位方式。

  • 非替换元素( non-replaced elements ):如果元素内容包含在文档中,则称为非替换元素。例如包含文字的段落。

  • 替换元素( replaced elements ):指用作为其他内容占位符的一个元素。 img 元素就是其中经典例子。

  • 块级元素( block-level elements ):块级元素会在其框之前和之后生成"换行",处在正常流中的块级元素会垂直摆放。

  • 行内元素( inline-level elements ):行内元素不会独占一行,相邻的行内元素会排列在同一行中。

  • 包含块( containing block ):CSS规范中制定一系列规则来确定元素的包含块。对于正常流中元素,包含块由最近块级祖先框的内容边界( content edge )构成。详情见CSS规范10.1章节 Definitionof"containing block" ,注意与块容器区分( block container )。

注:上述部分内容引自《CSS权威指南 第三版》第七章

在进一步介绍行内格式化之前,先来回顾一些行内布局的基本术语。(略枯燥,不过明白概念才能更好理解视觉效果)

基本术语

  • 匿名文本( anonymous text ):指所有未包含在行内元素的字符串。 CSS 规范中对匿名文本所处上下文分别有匿名块级框( anonymous block boxes )和匿名行内框( anonymousinlineboxes )生成规则。(详情见CSS规范:9.2.1.1、9.2.2.1章节)

  • 匿名行内框( anonymousinlineboxes ):任何被直接包含在一个块容器元素中(不在行内元素里面)的文本,必须视为一个匿名行内元素,生成匿名行内框。

  • 内容区( content area ):在非替换元素中,内容区可以是元素中各字符的em框串在一起构成的框,也可以是由元素中字符字形描述的框。替换元素中,内容区是元素固有宽高加上可有的外边距、边框或内边距。注意非替换元素内容区的高度应该基于字体, CSS 规范对此没有作具体说明。

  • 行间距( leading ):行间距为 line-heightfont-size 值之差。差值分为两半(半间距 half-leading )分别应用到内容区的顶部和底部。注意行间距只应用于非替换元素,可以为负值。

  • 行内框( inlineboxes ):行内框是通过向内容区增加行间距来描述。对于非替换元素,元素行内框的高度刚好等于 line-height 值。对于替换元素,元素行内框的高度等于内容区高度。

  • 行框( line boxes ):包含同一行中出现的行内框的最高点和最低点的最小框。换句话说,行框的上边界要位于最高行内框的上边界,而行框的底边要放在最低行内框的下边界。

注:上述部分内容引自《CSS权威指南 (第3版)》第七章

以上术语在初次阅读情况下,看起来还是比较费解,建议参照《CSS权威指南(第3版)》第七章理解。如下图所示:假如行内非替换元素的 font-size 为15px, line-height 为21px,则行间距为6px。相同行只存在唯一行内框,所以行框高度和行内框相同都为21px。

对于行框和行内框关系,可以简述为:行内框是由行内元素产生,行框则是一行中所有的行内框构成。

在继续介绍之前,有必要了解行内如何逐步构造一个行框,并且通过格式化过程清楚如何确定各部分高度。

视觉格式化流程

  1. 按照以下步骤确定行中各元素行内框以及高度:

    1. 获取各行内非替换元素及不属于后代内元素的所有文本的 font-size 值和 line-height 值, line-heightfont-size 差值为行间距值。

    2. 获取替换元素高度及上、下外边距,上、下内边距,上、下边框值相加。

  2. 对于内容区,需要确定各元素、匿名文本以及该行本身基线的位置,将其基线对齐。对于替换元素,需要底边放置该行基线上。

  3. 对于指定 vertical-align 值的元素,确定其垂直偏移量。并改变元素在上方或下方超出的距离。

  4. 确定各行内框的具体位置后再进行确定行框。行框高度为最高行内框顶端和最低行内框低端之间的距离。

其他要点

  1. 行内元素的背景应用于内容区及所有内边距。

  2. 行内元素的边框要包围内容区及所有内边距和边框。

  3. 垂直方向替换元素和非替换元素的视觉表现不尽相同。非替换元素的内、外边距和边框对其生成的框没有垂直效果,它们不会影响元素行内框的高度,以及该元素所在行框的高度。

  4. 替换元素的外边距和边框会影响行内框高度,也可能影响行框的高度。

上述流程已经对于行内元素格式化做一个大概介绍。但其中还有些细节问题,如何确定行框的基线位置,行内替换元素为什么会出现空隙(文章首部图所示), line-height 如何影响行高, vertical-alignline-height 又有何关系, vertical-align 又是如何影响视觉格式化等等。

如何确定行框的基线

在行内相关模型中,凡是涉及到垂直方向的排版或对齐,都离不开最基本的基线( baseline )。那基线又是如何确定的呢?

匿名文本x的下边缘(线)

这里指不包含在行内元素中的匿名行内框里面的字符 x 的底部。具体测试方法可在包含块中添加匿名文本 x 查看与该行图片底部位置,如下图所示。

演示请点击 codepen.io/sunpeijun/p…

行内替换元素间为什么会出现空隙

回到开篇所提到的问题,如上图所示,两张图片间为什么会出现莫名间隙?前面铺垫那么多,也是为了这块说道时更方便。首先这里有个前提,图片元素为行内替换元素,html采用 rem 布局,设置根元素 font-size 为100px,图片元素 font-size 继承根元素节点值100px。代码结构如下:

<div>  <img src='./images/bg1.jpg'>  <img src='./images/bg2.jpg'></div>

演示请点击 codepen.io/sunpeijun/p…

很明显的是每张图分别处于一个行框中。对于 line-height 默认值 CSS 规范中建议用户代理设置介于1.0到1.2。同时 line-height 值是相对于元素本身的 font-size 值计算,则这里图片元素 line-height 为100px(假设用户代理设置 line-height 默认为1)。图片作为行内替换元素,其在行内默认会与行框基线对齐。这里用户代理会产生空的匿名文本行内框,框的高度则等于 line-height 值。

由于高度值较小图片元素生成的行内框高度小于匿名文本行内框值(100px),行框高度为同一行中出现的行内框的最高点和最低点的最小框,很容易看出这里行高为100px。图片高度小于行高,视觉上会出现图片周围存在空白,就是上图中所示莫名空隙。其实主要原因还是 line-height 值过大撑起行框导致。

那么如何解决这个问题呢?

  1. 设置图片包含块( containing block ) line-height:0 ,此时空匿名文本 line-height 继承包含块值,行内框值为0。那么行框边界则为图片元素生成的行内框最高点和最低点,行框高度等于图片原有高度。

  2. 设置图片包含块( containing block ) font-size:0 ,空匿名文本框继承 font-size 值, line-height 计算值也为0,原理同上。

  3. 设置图片元素 display:block 。这么做目的是阻止产生行内格式化模型,因此也不存在 lin-height 计算值、行框、行间距属性。

  4. 设置图片包含块样式属性为 display:flex;flex-direction:column; ,本质上也是阻止产生行内格式化上下文。

演示请点击 codepen.io/sunpeijun/p…

附: CSS 规范中对空匿名文本行内框描述(详情见 CSS 规范 10.8.1章节)

each line box starts with a zero-width inline box with the element's font and line height properties.

line-height 是否绝对等于行高

这里也可以换种表述方式, line-height 属性和行框的高度有何关系。查阅 CSS 规范, line-height 描述如下:

On a block container element whose content is composed of inline-level elements, 'line-height' specifies the minimal height of line boxes within the element. The minimum height consists of a minimum height above the baseline and a minimum depth below it, exactly as if each line box starts with a zero-width inline box with the element's font and line height properties.

笔者不才,尝试翻译。对于一个内容由行内级元素组成的块容器元素, line-height 指定元素内行框的最小高度。这个最小高度包含基线上方的最小高度和下方的最小深度。就像每个行框以一个具有该元素字体和行高属性的宽度为0的行内框开始。(笔者注:也就是上文提到空匿名文本行内框)

注意规范中有明确表示, line-height 指定元素内行框的最小高度。由此看来 line-height 值并不是绝对等于行框的高度。那如何去理解最小高度呢?其实这个问题的范畴已经在上文隐式提及过。

首先行框中包含替换元素和非替换元素,对于非替换元素,元素行内框的高度则等于 line-height 值。对于替换元素, line-height 属性无效,元素行内框的高度等于内容区高度(见上文基础术语行内框定义)。参照上文 视觉格式化流程 章节,假如行框内存在替换元素( img )的行内框高度大于非替换元素行内框高度( line-height 值),又因行框的高度为行内框的最高点到最低点距离,所以此时行框的高度要大于 line-height 值。

那什么情况下,行高等于 line-height 值,也就是前面所说的最小高度呢?显而易见,行框中只存在非替换元素或存在替换元素,但替换元素行内框要小于 line-height 值。(多个行内框的情况下需具有相同 line-height 值)

line-height 与 vertical-align 有何关系

谈及 line-heightvertical-align 关系,张鑫旭老师有篇博文描述为 基友 。为何基友呢,抛个问题: line-height 值为百分比时如何计算, vertical-align 值为百分比时又是相对于谁呢?其实前者相对于元素 font-size 计算,后者相对于元素 line-height 值计算。

心生疑惑, vertical-align 百分比值为何要相对于 line-height 值计算?一般我们认为元素在垂直方向相关属性百分比值,多是根据元素包含块的高度或本身高度计算,比如 height、top、translateY 等。不过对于行内非替换元素垂直方向 height、margin、padding 属性是不生效的,那么退而求其次能确定的也就是 line-height 值了。基友关系也就油然而生,个人理解非权威。

vertical-align 如何影响视觉格式化

再来看一个栗子,和 行内替换元素间为什么会出现空隙 章节文档结构一致,两张图片包含在块级元素中,但并未设置 html 元素 font-size:100px; 属性。为减少图示方便说道,添加匿名文本 x 作为辅助,如下图所示。

演示请点击 codepen.io/sunpeijun/p…

首先这里并没有设置html元素 font-size:100px ,行内框的高度也不会出现过大(等于100px)现象,那为什么还会出现空白间隙呢?

因为空匿名文本行内框。

行内替换元素垂直方向默认采用 baseline 对齐,替换元素底部放置在行框基线上。此时行内会产生空匿名文本框确定行框基线位置(以上图字符 x 行内框为例)。但文本行内框的底部在基线下面,且行框的底端应包含文本框底端。也就是说行框包含了基线位置下面空白。图示间隙其实是文本框基线以下至文本框底部的区域。

知其原因后,解决起来也就顺理成章。除上文 行内替换元素间为什么会出现空隙 章节提及的方法可用外,这里我们还可以使用 vertical-align 属性来解决。图片元素设置:

img {  vertical-align: top;}

演示请点击 codepen.io/sunpeijun/p…

为什么? vertical-align:top 属性使图片元素垂直方向与行框顶部对齐,自然文本框底部也不再是行框的底部,图片生成行内框的高度也就成为行框的高度(限于 line-height 值小于图片高度情形)。

参阅规范, vertical-align 属性会影响由一个行内级元素生成行内框在行框内部的竖直定位。由此可知, vertical-align 在不同属性值情况下,对行框的布局和高度会产生相应影响。

vertical-align: middle 是否绝对垂直居中

vertical-align 还有个耐人琢磨的属性值: middleCSS 规范定义其为:

Align the vertical midpoint of the box with the baseline of the parent box plus half the x-height of the parent.

简译之,把该框的垂直中点和父级框的基线加上父级的半 x-height 对齐。 x-height 又是什么意思,其实这里是指小写字符 x 的高度。术语描述就是基线和等分线( mean line )(也称作中线( midline ))之间的距离。维基百科中图示如下:

vertical-align:middle 其实并不会绝对垂直居中,我们平常看到的 middle 只是一种近似的效果。原因很简单,因为不同的字体,其在行内盒子中的位置是不一样的。

比方说“微软雅黑”就是一个字符下沉比较明显的字体,所有字符的位置相比其他字体要偏下一点。你会发现图标和文字不在一条线上,而是相对于字符 x 的中心位置对齐,我们肉眼看上去就好像和文字居中对齐了。

顺便延伸下, height=line-height 处理方式对于单行文本来说是绝对的垂直居中吗?

注:上述部分内容引自张鑫旭老师博文《字母’x’在CSS世界中的角色和故事》。

结语

尽管 CSS 格式化模型的某些方面乍看起来有些不太直观,不过多熟悉一些就会发现这其中是有道理的。相对于块级元素,行内元素在视觉格式化方面更为晦涩难懂。同时行内元素还要考虑替换元素和非替换元素情形,替换元素的加入也使得视觉格式化方面略显复杂。

此外行内元素涉及到的概念或术语多是不可见的,比如匿名框( anonymous boxes )、行内框( inlineboxes ),行框( line boxes ),但实际却起着重要作用。同时又有 font-size、line-height、vertical-align 属性左右着行内布局。一知半解或浅尝辄止终不能深入,静下来慢慢阅读会收获颇丰。

本篇文章就书写至此,如果想对格式化方面理解更深,还请多读《CSS权威指南》多读 CSS 规范。书中自有千钟粟。

初写技术文章,多担忧某处误导读者。下笔数日,仍会有不严谨之处。如有疑问或错误,敬请斧正,先行谢过。

附录

参考文献

  1. 《CSS权威指南(第3版)》

  2. CSS 2.1规范

  3. CSS 2.2规范

  4. CSS 2.1中文译文

  5. CSS深入理解vertical-align和line-height的基友关系

  6. 字母’x’在CSS世界中的角色和故事

  7. 维基百科:x-height

打个广告,欢迎关注笔者的公众号

注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。