文章摘要:在页面布局中,相比块元素独自霸占一行,行内元素可以和其他的行内元素共处一行,其布局相对复杂。在行内元素中,还分为行内块元素,行内置换元素,行内非置换元素以及匿名文本,他们在布局时的表现也有差异,本文带你了解行内元素如何在浏览器中进行布局。
行布局
下面给出一个简单的例子,一个被分成多行展示的 span 元素(给出边框样式,其他什么都不做)。
"border: 1px solid red">This is text held within a span element which is inside a containing element(a paragraph, in this case). The border shows the boundaries of the span element
在浏览器中展示的结果如下:
从上图的效果可以看到,内联元素被分成多行展示时,一行会从左向右(从左向右书写的语言中, 从右向左的语言反之亦然)依次放置,不够显示时会折到下一行。
当行内元素在展示时会形成一个个的行框,这些行框之间相互紧挨着。在不设置 padding 的情况下给行内元素设置 border,border 在每个行框的下一个像素处绘制,因此会看到两行之间的 border 有重叠现象。
理解行内元素的布局,就是要理解行内元素所在的行框是如何生成的
基本概念和术语
1.匿名文本
匿名文本是指不被包含在任何 内联元素
中的文本
<p> I'm <em> so em> Happyp>
比如上图的代码,I'm 和 Happy 是被包裹在块级元素 中的,so 是被包裹在行内元素
中的,因此只有 I'm 和 happy 是匿名文本。
2.字体框
html 中文本的字体信息被定义在字体文件中,css 属性中的 font-size 就是用来控制 字体高度
的。每个字体还有字形,字形框可能比字体高也可以比字体小。
3.内容区
内联非置换元素和内联元素的内容区有所区别:
内联非置换元素:内联非置换元素是指内容直接展示给浏览器的元素,比如 span, label 等元素。内联非置换元素的内容区可以是内联元素的字体框也可以是字形框(浏览器可以任选一个来使用)。本文我们为了简化情况假定浏览器使用了字体框,也就是说 font-size 决定了内联非置换元素的内容区高度(字符的字形框通常会在字符的上下部分添加一些空白,因此比字体框会稍大一些)。
内联置换元素:内联置换元素是指浏览器需要根据标签的内容和属性来决定展示内容的元素,比较典型的有 img 元素还有 input 元素(需要根据 type 属性来确定具体展示的内容)。内联置换元素的内容区包括它自己本身的宽高,以及它周围的 padding,border 和 margin。
4.行距
只有内联非置换元素才有行距,内联置换元素没有行距。
对于内联非置换元素来说行距的值为 css 中的 line-height 属性的值和 font-size 值的差值。在对行内非置换元素布局时,line-height 和 font-size 的差值会被均分到字符内容区的上下部分。
假设上图中段落中的文本字体大小为 16px,行高为 18px,。那么每行将有 18px-16px = 2px
的像素被分配到文本的上下部分,这每一部分叫半行距。由于行距与行距之间是不会折叠的,因此行与行之间就有 line-height - font-size 的间距(行距)。
5.行内框
每一个行内元素在布局的过程中,都会生成自己的行内框。行内框是根据自身内容区和行距得到的。
内联非置换元素:根据公式,内容区 + 行距 =font-size + (line-height - font-size)= line-height,因此内联非置换元素的行内框高度就是由 line-height 的值来决定的。
内联置换元素:由于内联置换元素没有行距,因此它的行内框就是它的内容区高度。内联置换元素的内容区为自身的宽高,padding,margin,border 构成。
6.行框
内联元素在布局的过程中当构成一行时会生成一个行框。行框是过每一行中最高的行内框顶部和最低的行内框底边之间的空间组成的。换句话说,行框能包裹住一行中所有的行内框。
如何确定行框的高度
行内元素在布局时会产生一个个的行框,理解行内元素的布局问题就需要理解行框是如何生成的。行框生成可以通过下面的步骤来确定:
确定行内各【非置换元素】和【匿名】文本的行内框
由内容区和行距决定,分别与 line-height 和 font-size 值相关
确定行内各【置换元素】的行内框
置换元素的行内框就是置换元素的内容区,自身的宽高加上盒模型属性(padding, border, margin)共同构成的区域
确定这些行内框在一行中的位置
默认情况下,行内框将自己的基线与一行的基线对齐
确定设置了 vertical-align 属性的元素偏移了多少
在各个行内框按照基线对齐之后,还可以通过 vertical-align 属性来调整行内元素生成的行内框相对于基线便宜的距离
确定每行的行框
在确定了每个行内元素的行内框之后,就能得到每一行的行框。每行的行框就是刚好过最高的行内框顶边以及最低的行内框的底边。
确定行内元素布局的基本步骤可以总结为下图,接下来为对每个步骤涉及到的关键点进行一个说明。
line-height
在确定行内非置换元素以及匿名文本的行内框时候,会用到 css 中的 line-height 属性。关于 line-height 属性,有一下几点需要注意
line-height 和 font-size 之间的差值会被均分成两部分,分别添加到元素的上下方。
所有元素都可以设置 line-height 属性,但是它只影响元素中的行内内容。
置换元素没有行距,但是为置换元素设置 line-height 会影响行内置换元素在行框中的纵向对齐位置(当为置换元素设置的 vertical-align 值为百分数的时候,就用用到该置换元素的 line-height 值)
line-height 可以被继承
在上图几种取值方式中,line-height 被设置为纯数字与其他几种方式稍有区别。这种区别主要体现在继承时。如果为 line-height 设置纯数字的值,那么子元素直接继承这个数字,然后和自身的 font-size 属性的值相乘得到最终的行内框高度。如果设置为其他的值,子元素继承的是在父元素上已经计算得到的 line-height 值。
class
= class="small"> class="big"> class="big-content">
<style> .parent { font-size: 15px; line-height: 1.5; } .small { font-size: 12px; } .big { font-size: 16px; line-height: 2em; } .big-content { font-size: 12px; }style>
如上图的实例代码,我们为 parent 这个元素设置了 font-size 和 line-height,分别为 15px 和 1.5
第一个直接孩子元素 small 没有自己的 line-height 值,会从父元素上继承,因此 small 元素对应的行内框高度为 12px * 1.5 = 18px;
第二个直接孩子元素 big 有自己的 line-height 值,因此自身的行内框高度为 16px * 2em = 32px;
big 元素的孩子元素 big-content 没有自己的 line-height,并且父元素的 line-height 值为带单位数值,因此会直接从父元素继承这个值为 32px;
确定基线的位置
当得到行内元素的行内框之后,默认先会将这些行内框按照基线进行对齐。因此,需要确定各个行内元素的基线位置。
确定元素的基线有一个诀窍:就是在该元素旁边写上字母 x,与 x 底边对齐的地方就是该元素的基线位置
对于行内非置换元素(无论是匿名文本还是行内非置换元素)的基线是内置在字体文件中的,除非编辑字体文件,否则无法修改元素的基线位置。
按照 x 作为参照物的方法,我们发现置换元素的基线位置为其下边距的外侧。如下图所示,为图片设置 border, padding 和正的 margin,在于旁边的文本对齐时,使用的是下外边距的外侧。
vertical-align
将每个行内元素产生的行内框按照基线对齐之后,可以通过 css 属性 vertical-align 将其按照基线进行偏移。但 vertical-align 这个属性有如下几点需要注意:
vertical-align 属性不能被继承
vertical-align 用来确定行内元素的基线和所在行的基线的对齐方式
vertical-align 只能用在行内元素上
vertical-align 属性会将元素的内容区和其行内框一起按照基线的位置移动
Strut(支撑)
按照上述步骤我们可以确定行内元素在页面中的排布,但是还有可以引发行内元素布局诡异行为的其他概念。
css 规范中在描述 line-height 时,给出了下面一段话
参考地址
从这段话中我们可以得到下面的两点关键信息:
line-height 指定了在块级元素中行盒子的最小高度
行框是需要包裹一行中所有行内框的,line-height 决定了行内框的高度,因此 line-height 确定了一行的行盒子最小高度。
这个最小高度包括了基线上面的最小高度和基线下面的最小深度。就如同每一行的开头都有一个行内盒子,这个行内盒子的宽度为 0,font-size 和 line-height 的值和最近的块级父元素的属性保持一致。
关于 strut 在后面的具体的 case study 进一步介绍
匿名文本、行内非置换元素的行
确定内容区
font-size 的值确定了匿名文本和行内非置换元素内容区的高度
确定行内框
匿名文本和行内非置换元素的行内框的高度由 line-height 的值确定。line-height 和 font-size 的差值被均匀分布在内容区的上下方。line-height 的值是可以小于内容区(font-size)的,此时行内框将垂直居中分布在内容区上;
将行内框按照基线对齐
"font-size:12px;line-height: 12px;"
>This which is "font-size: 24px">strongly emphasized and which larger than the surrounding text.
上图中的边框是行框的边,不是真实设置的元素的 border。由于 strong 元素的 line-height 比 font-size 小,导致该元素的内容比它生成的行内框高(有部分内容溢出了行内框)。将其按照基线进行对齐后,我们得到如上图的布局。第二行的行框的顶边是过最高的行内框(strong 元素)的顶边,以及最低的行内框(旁边的元素)的底边。由于 strong 元素有一部分内容溢出了行内框,行与行之间的行框是紧挨着的,所以第二行的 strong 元素叠加到了其他行上。
使用 vertical-align 属性调整行内框的位置
(1) 给 vertical-align 设定具体的值
"font-size:12px;line-height: 12px;"
>This which is "font-size: 24px; vertical-align:4px">strongly emphasized and which larger than the surrounding text.
vertical-align 设定了元素相对于当前行的基线移动的距离。上面的实例中将 strong 元素的 vertical-align 设置成 4px 是指将 strong 元素的内容区和行内框沿着当前行的基线向上移动 4px,这样会导致 strong 元素的行内框向上抬 4px,第二行的行框整体高度也增加了 4px(当前行的行框要过当前行中最高位置的行内框的顶边,和最低位置行内框的底边)
(2)设定 vertical-align 为 top 关键字
<p style="font-size: 12px; line-height: 12px;">This is text, <em>some of which is emphasizedem>,<br>plus other text that is <strong style="font-size: 24px;">strongstrong>and <span style="vertical-align: top;">tallspan> and is<br> larger than the surrounding text.p>
关键字 top 的意思是将当前元素生成的行内框的顶边和当前行框的顶边对齐,span 元素的 font-size 和 line-height 相等,因此文本 tall 的行内框刚好包裹住其内容区。tall 的行内框顶边和当前行的行框顶边对齐,当前行框的顶边就是最高的行内框顶边,也即 元素的顶边,效果如上图位置所示。
将文本 tall 的 line-height 缩小到小于它本身字体的大小,tall 文本所在的行内框在 tall 文本的内部(内容区中间的位置),然后将行内框顶边与一行的行框顶边(strong 元素的行内框顶边)对齐,效果如下图所示:
盒模型属性对行框的影响
padding margin, border 在行内非置换元素上没有纵深效果,不会影响元素的行高;但是在水平方向上会影响元素的排布,如果 margin 为负值,那么可能会导致文本在水平方向重叠。
border 是包裹在内容区外的 内边距外侧 ,不受 line-height 的影响
"font-size:12px;line-height: 12px;"
>This which is "font-size: 24px; border: 5px solid red;padding: 10px;">strongly emphasized and which larger than the surrounding text.
为 strong 元素设定了 border 和 padding,并没有增加它的行高,strong 元素依然会和上下两行重叠。并且为 strong 元素设定的 border 是包裹在 padding 的外侧,没有受到 line-height 值的影响。
strut 对匿名文本或者行内非置换元素的影响
<span style="font-size: 12px; line-height: 12px;"> This is text, <em>some of which is emphasizedem>, plus other text<br> which is <strong style="font-size: 24px;">strongly emphasizedstrong> and which is<br>larger than the surrounding text.span><hr/><span style="font-size: 12px; line-height: 0.5;"> This is text, <em>some of which is emphasizedem>, plus other text<br> which is <strong style="font-size: 24px;">strongly emphasizedstrong> and which is<br>larger than the surrounding text.span><hr><span style="font-size: 12px; line-height: 2;"> This is text, <em>some of which is emphasizedem>, plus other text<br> which is <strong style="font-size: 24px;">strongly emphasizedstrong> and which is<br>larger than the surrounding text.span>
渲染的结果如下:
与前面的例子不同的是,我们将父元素
更换成一个行内元素,其他保持不变。根据前面介绍的知识,第一个例子和第二例子中的 strong 元素应该会叠加到其他行,但是这里并没有出现叠加的现象。第三个例子,strong 元素的 line-height 值为自身 font-size 的两倍,又确实增加了第二行的行高。
这个现象可以由前面介绍的 strut 概念来解释。每一个行框的开头都有一个宽度为 0 的行内盒子,这个盒子的 font-size 和 line-height 值和最近的那个块级父元素保持一致。上面三个例子中,他们最近的块级父元素都是 body 元素(font-size 默认为 16px,line-height 为 normal(用户代理会字形设置一个合理的值,不会小于 font-size,也不会超过 font-size 的两倍)),所以这个隐形的行内盒子的高度是一个 line-height 大于 16px 小于 32px 的值。根据前面介绍的行内布局,行高是过一行中最高的行内盒子的顶边和最低的行内盒子的底边之间的宽度,那么第一个例子(line-height 和 font-size 相等,都为 12px)和第二个例子(line-height 为 0.5, 第一行和第三行可见元素的行高为 6px, 第二行因为有 font-size 为 24px 的 strong 元素,所以第二行的行高为 12px,依然小于这个隐形行内盒子的高度)的行高都是这个不小于 16px 的 line-height 的值。第三个例子(line-height 为 2,是 font-size 的两倍)中可见的元素的行高是大于这个隐形的行内盒子的,因此渲染的结果是 第二行的行高增加了。
strut 解决方案
1.对折成多行展示的行内元素设置 display:block 或者替换成块级元素
<span style="display:block;font-size: 12px; line-height: 0.5;"> This is text, <em>some of which is emphasizedem>, plus other text<br> which is <strong style="font-size: 24px;">strongly emphasizedstrong> and which is<br>larger than the surrounding text.span>
这样设置可以让这个隐形的行内盒子和这些匿名文本,行内元素使用相同的 font-size 和 line-height 值。
2.在这个行内元素外包裹一层块级元素,并将这个块级元素的 font-size 设置为 0
<div style="font-size:0;"> <span style="font-size: 12px; line-height: 0.5;"> This is text, <em>some of which is emphasizedem>, plus other text<br> which is <strong style="font-size: 24px;">strongly emphasizedstrong> and which is<br> larger than the surrounding text. span>div>
将 font-size 设置为 0 等同于删除了这个隐形的元素,那么这个隐形的元素不会再生成对应的行内盒子影响改行的布局。
备注:通常情况下我们不需要解决这个问题。strut 就是因为浏览器尽量避免行与行之间产生折叠
行内置换元素的行
确定内容区
行内置换元素的内容区为自身的宽高和盒模型属性(margin, padding, border)共同构成的区域
确定行内框
行内置换元素的行内框就是置换元素的内容区本身
将行框按照基线对齐
置换元素的没有基线,它使用下外边距的外侧和一行的基线对齐,因此可以间接的将下外边距的外侧当做它的基线;
下方的例子对图片设置了 border, padding,以及正的 margin,可以看到图片在与旁边的文本对齐时 border 和旁边的基线有一定的空白,这个空白就是由正 margin 产生的。同样第二行的行框也包裹住了图片的上外边距。
"font-size: 12px; line-height: 1.5;"> This paragraph contains an img element. This element has been given a height that is larger than a typical line box height for this paragraphs, "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1579668285454&di=e150936ccd8f63f4dc074084f8acbdd9&imgtype=0&src=http%3A%2F%2Fpn.gexing.com%2FG1%2FM00%2FC6%2F0B%2FrBACJlYdsrXxqLeoAABgiuXP4Dw932.jpg" width="100px" style="border: 2px solid red; padding: 10px; margin:10px 0"> which leads to potentially unwanted consequences. The extra space you see between lines of text is to be expected.
</div>
外边距为负值可以导致置换元素和其他的行重叠。下外边距的边缘作为置换元素的基线,为负值的时,相当于将元素的基线线上提;上外边距为负,相当于置换元素的行内框顶边向下移。得到的效果如上图所示
"font-size: 12px; line-height: 20px;"> This paragraph contains an img element. This element has been given a height that is larger than a typical line box height for this paragraphs, "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1579668285454&di=e150936ccd8f63f4dc074084f8acbdd9&imgtype=0&src=http%3A%2F%2Fpn.gexing.com%2FG1%2FM00%2FC6%2F0B%2FrBACJlYdsrXxqLeoAABgiuXP4Dw932.jpg" width="100px" style="margin:-20px 0;"> which leads to potentially unwanted consequences. The extra space you see between lines of text is to be expected.
</div>
使用 vertical-align 调整行内框的位置
使用 vertical-align 调整行内框的位置
(1) vertical-align 为具体的数值
"font-size: 12px; line-height: 20px;"> This paragraph contains an img element. This element has been given a height that is larger than a typical line box height for this paragraphs, "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1579668285454&di=e150936ccd8f63f4dc074084f8acbdd9&imgtype=0&src=http%3A%2F%2Fpn.gexing.com%2FG1%2FM00%2FC6%2F0B%2FrBACJlYdsrXxqLeoAABgiuXP4Dw932.jpg" width="100px" style="margin:-20px 0;vertical-align:20px;"> which leads to potentially unwanted consequences. The extra space you see between lines of text is to be expected.
</div>
vertical-align 是将元素基于基线的位置向上(下)移动一定的距离。在上述例子中,margin-bottom 为-20px 将元素的基线向上提了 20px(可以看做元素基于其下外边距的边缘向下移动了 20px),加上 vertical-align 设置为 20px,也就是将元素基于基线向上移动 20px;这样一来就等同于图像的底边和这一行的基线对齐。该实例中没有 border,否则应该是下边的 border 的边缘和这一行的基线对齐。
(2) vertical-align 设为百分数
在前面介绍 line-height 时介绍过行内置换元素是没有行距的,因此 line-height 属性是不会影响置换元素的行内框高度的。但是在使用 vertical-align 并且设置其值为百分数时,置换元素会使用这个值。当 vertical-align 的值为百分数时,计算永远是相对于 line-height 的值来进行的。下面的例子将图片的 vertical-align 设置成 50%,等同于将其设置为 40px(继承自 div 元素的 line-height 属性)* 50% = 20px,得到的效果和上例一样。
"font-size: 12px; line-height: 40px;"> This paragraph contains an img element. This element has been given a height that is larger than a typical line box height for this paragraphs, "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1579668285454&di=e150936ccd8f63f4dc074084f8acbdd9&imgtype=0&src=http%3A%2F%2Fpn.gexing.com%2FG1%2FM00%2FC6%2F0B%2FrBACJlYdsrXxqLeoAABgiuXP4Dw932.jpg" width="100px" style="margin:-20px 0;vertical-align:50%;"> which leads to potentially unwanted consequences. The extra space you see between lines of text is to be expected.
</div>
盒模型属性对行框的影响
盒模型属性对行框的影响
从前面的分析已经不难看出,盒模型属性作为内容区的一部分,会对置换元素的行内框产生影响,进而影响它的行框
strut 对置换元素的影响
strut 对置换元素的影响
"border: 2px solid red;font-size:0"> "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1579668285454&di=e150936ccd8f63f4dc074084f8acbdd9&imgtype=0&src=http%3A%2F%2Fpn.gexing.com%2FG1%2FM00%2FC6%2F0B%2FrBACJlYdsrXxqLeoAABgiuXP4Dw932.jpg" height="100px">.
</div>
在上面的代码中 img 作为 div 的唯一后代元素,但是 img 和 div 的底边之间仍然有空白产生。这就是因为前面介绍过的每一行的前面都有一个隐形的元素,其 font-size 和 line-height 与最近的父元素保持一致。因此图像和这个隐形的元素的基线对齐,基线并不是字符的底边(一般偏字体的中下部分,可以通过元素 x 的底边查看),因此图片和父元素的底边会产生空白。
strut 解决方案
strut 解决方案
(1)在块级父元素上设置 font-size 为 0,等同于删除了这个隐形的元素
(2)对行内置换元素设置 display:block 禁止它生成行内框
行内块元素的行
行内块元素的行
行内块元素和行内置换元素表现的基本一致,但是也有些许差别(主要体现在基线的位置上)。利用 x 作为参照物的方法,可以得到行内块元素的基线为其最后一行行框的基线。
浮动和定位对行内元素的影响
浮动和定位对行内元素的影响
浮动和定位(relative 除外)都会让行内元素生成块级框
弹性盒布局对行内元素的影响
弹性盒布局对行内元素的影响
行内元素在弹性布局上下文中不再生成行内框而是生成块级框,因此不会再产生上述 strut 的问题,其布局受弹性盒布局的影响。
class="inline-flex2">
.inline-flex2 { display: flex; background-color: green;}.inline-flex2 span { display: inline-block; width: 5em; height: 5em; background-color: red;}
声明为 display:inline-flex 的弹性盒容器,会生成行内块框,布局方式同行内块框,但是基线有所不同。其基线的位置为第一行内容的基线位置,而不是最后一行内容的基线位置。如下面的例子所示:
class="inline-flex">xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</div> xxxxxx
.inline-flex {
display:inline-flex;
width: 10em;
height: 10em;
background-color: silver;
word-break: break-all;
}
总结
总结
行内元素在布局时的大致流程如下: