先记住这个答案
span 属于行内非替换元素,它的内容区高度由字体决定,CSS 规范明确规定 width 和 height 对它不生效。上下 margin 在计算上存在,但不参与行框的高度,所以看不出任何位移。上下 padding 和 border 会画出来,同样不撑开行,常常盖到上一行的文字上。一行有多高,只由这一行里各个行内盒的 line-height 决定。想让它认宽高,就改成 display: inline-block 或 block。
- 行内非替换元素不认
width和height - 行内元素垂直外边距不撑行高
- 垂直内边距和边框可绘制越界
- 改成
inline-block就能设宽高和上下外边距
同一个 span,两种 display 的对比
先看一段最直接的对照。同样的样式,一个是默认的 inline,一个改成了 inline-block。
<p>第一行文字<br>
<span style="height: 60px; margin-top: 30px; padding: 10px; background: #fcd34d;">inline</span>
后面的文字
</p>
<p>第一行文字<br>
<span style="display: inline-block; height: 60px; margin-top: 30px; padding: 10px; background: #fcd34d;">inline-block</span>
后面的文字
</p>
<!-- 第一段:黄色背景比文字高一截,盖到了上一行,但第二行没有下移 -->
<!-- 第二段:黄块高 80px(60 + 上下 padding),第二行被整体推低 -->第一段里,height: 60px 完全被忽略,margin-top: 30px 没有把它往下推,上下各 10px 的 padding 让背景变高了,但这一行的高度没变,背景就溢到上一行上。第二段改成 inline-block 后,它在外部仍然像文字一样排在行里,内部却按块级盒计算尺寸,宽高、上下 margin 都参与了行框高度的计算,整行被撑高。
为什么行内元素不认高度
行内元素的设计初衷是「嵌在一段文字里的一小段文字」。比如一句话里有个 <a> 链接,它可能从第一行的末尾折到第二行开头,被拆成两段。这种盒子本来就没法有一个固定的宽高,所以规范干脆不让 width 和 height 作用在它身上。
那一行的高度怎么定呢?浏览器会给每一行生成一个行框,可以理解成一条横向的轨道。轨道里每个行内盒都按自己的 line-height 算出一个高度,再按 vertical-align 对齐,最高点到最低点的距离就是这一行的高度。这个计算过程只用 line-height,不看 margin、padding 和 border。所以想让 span 所在的行更高,给它设大一点的 line-height 是有效的。
水平方向就不一样了。左右的 margin、padding、border 会实实在在地占位,把前后的文字推开。所以才会出现「margin-left 有效、margin-top 无效」这种看起来不对称的现象。
- 哪些情况下 span 会变得能设高度
- 除了手动写
display: inline-block,有几种情况会让span自动变成块级盒,宽高随之生效:设置了float,设置了position: absolute或fixed,或者它的父元素是 flex、grid 容器,这时它成了 flex 或 grid 项目。面试时能说出这几种「自动变块」的情况,说明你真的理解display的计算值会被改写。
容易答错的地方
- 说行内元素的所有 margin 都无效
- 只有上下方向无效,左右
margin是正常的。设置margin: 0 20px能把左右文字推开20px。 - 说垂直 padding 也无效
- 垂直
padding是生效的,背景色会跟着变大,只是它不占位,不会把上下行推开。所以给行内的链接加上下padding扩大点击区域是可行的,但要注意背景会压到上一行。 - 把 img 也归进来
<img>默认也是inline,但它是替换元素,内容来自外部资源,本身有固有尺寸,所以能设width、height,上下margin也会撑开行框。题目里的规律只适用于非替换元素。
面试官还会怎么问?
那怎么让一个 span 垂直方向留出间距?
最常见的是改成 display: inline-block,然后直接设 margin-top 或 height。如果只是想让整行更高、文字更松,给它或它的父元素加大 line-height 就够了,不需要改 display。
inline-block 元素之间为什么有一条缝?
因为 inline-block 仍然参与行内排版,HTML 里两个标签之间的换行和空格会被当成一个空格字符渲染出来。常见修法是父元素 font-size: 0,或者干脆改用 flex 布局。
inline-block 的元素底部为什么会多出几像素空白?
inline-block 和图片默认按基线 vertical-align: baseline 对齐,基线下面要给字母 g、y 的下伸部分留位置,于是底部空出一截。把 vertical-align 改成 top 或 middle,或者改成 display: block 就没了。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。