浅析行内元素视觉格式化

起因

前段时间组内同学在开发运营页的过程中,遇到一个有趣的问题:移动端页面采用 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

                                     

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


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值