写 CSS 写久了,大家都背过一条规则:display: inline 的元素不能设置宽高,vertical-align 只对行内元素有意义,诸如此类。但很多人会发现一个奇怪的现象:<img> 明明是行内元素,却可以随便设置 width 和 height;一个 <span> 却不行。
这时候如果去查 MDN 或者规范,会看到一个词:replaced element(替换元素)。这篇文章就来把这个概念讲清楚:它是什么、为什么会有这种设计、以及它到底怎么影响你写的 CSS。
一、先说结论:replaced element 是什么
Replaced element(替换元素)指的是那些”内容不是由 HTML/CSS 直接描述,而是由外部资源或者浏览器内部机制来渲染”的元素。
说人话就是:这类标签本身不包含”内容”,它只是一个占位符,真正显示出来的东西是浏览器从别处拿来塞进去的。
典型的替换元素包括:
<img>—— 内容是外部图片文件<video>/<audio>—— 内容是媒体流<iframe>—— 内容是另一个完整的文档<canvas>—— 内容是脚本绘制的位图<object>/<embed>—— 内容是插件或外部资源- 表单控件如
<input>、<textarea>、<select>—— 内容由浏览器根据操作系统/浏览器引擎渲染成控件外观
而像 <div>、<span>、<p>、<a> 这些标签,它们的内容就是你写在标签里的文字或子元素,浏览器直接根据 HTML 结构和 CSS 规则把内容画出来。这些叫非替换元素(non-replaced element)。
一句话总结这个区别:
非替换元素的”长相”完全由 CSS 决定;替换元素的”长相”首先取决于它加载的外部内容本身,CSS 只能在有限范围内去调整这个外部内容的展示方式。
二、为什么会有这种区分?
这不是浏览器厂商随便拍脑袋定的规则,而是 CSS 规范里一开始就存在的设计,原因其实很朴素。
CSS 的排版模型(尤其是 inline formatting)是围着”文字”设计的:字体、行高、基线对齐,这些概念都假设内容是一串可以被度量、被排版引擎理解的字符。
但一张图片、一段视频不是文字,它是一块有自己固有尺寸(intrinsic size)的矩形内容。
浏览器没办法用排版文字的逻辑去处理一张图片,因为图片没有”字符宽度”这种东西,它只有像素宽高。
所以规范专门为这类元素开了一个口子:它们虽然可以像行内元素一样参与到文字排版的流里(比如让一张小图和文字混排),但同时又保留了”盒子”的一些块级特性,比如可以设置宽高、可以有 vertical-align。
这就是为什么替换元素常被称为”行内块级混合体“:它们默认是 inline,但行为上更接近 inline-block。
三、具体表现在哪些差异上?
1. 宽高设置是否生效
span { width: 200px; height: 100px; } /* 无效,span 不响应宽高 */
img { width: 200px; height: 100px; } /* 有效 */
普通行内元素(span、a、strong)设置 width/height 是不生效的,因为行内布局里,一个元素的”宽”是由它包裹的文字内容撑开的,浏览器根本不认为你可以指定它的宽度。
但 img 这类替换元素不一样。它的内容是一张位图,位图天然有宽高属性,浏览器允许你用 CSS 覆盖它的显示宽高(哪怕和图片原始尺寸不一致,也就是常见的”拉伸变形”效果)。
2. 固有尺寸(intrinsic size)
替换元素有一个非替换元素没有的概念叫固有尺寸。比如一张 800×600 的图片,如果你不写任何 CSS,它默认就按 800×600 显示,这个尺寸是从图片文件本身”带过来”的,不是 CSS 计算出来的。
aspect-ratio(宽高比)也是同理:替换元素天然有固有宽高比,这也是为什么现代 CSS 里 object-fit、object-position 这两个属性只对替换元素有意义,普通的 div 用不上。
3. vertical-align 的对齐基准
行内替换元素在和文字混排时,默认的基线对齐方式和普通文字不同。一个 img 默认会以自己的底边对齐文字的基线,这也是为什么很多人会发现”图片下面莫名多出一条缝”,本质上就是图片作为替换元素参与行内排版基线对齐的表现,常见解决办法是给图片加 display: block 或 vertical-align: bottom。
4. 伪元素不生效
::before 和 ::after 在替换元素上通常不起作用(具体规范里是”未定义行为”,各浏览器基本都不渲染)。因为替换元素的”内容框”是留给外部资源的,浏览器不知道该把伪元素内容画在哪一层。
所以你没法给 <img> 加 ::before 来实现装饰效果,只能包一层 div 或者用 ::after 加在父容器上。
5. CSS 排版属性大多不生效
font-size、line-height、text-align 这些文字排版属性对替换元素本身的内容没有意义(它又不是文字),但它们仍然可能影响替换元素所占的排版空间,比如某些浏览器里 line-height 会影响图片所在行的行高计算。这也是初学者容易困惑的地方:属性”看起来没用”,但又不是完全没有关联。
四、一个容易被忽略的点:display 值不改变”是否替换元素”
很多人会误以为给 span 设置 display: inline-block 就能让它变得”和 img 一样”。这里要澄清一下:是否是替换元素,是标签语义决定的,和 display 属性无关。
display: inline-block 只是让 span 在盒模型层面获得类似替换元素的”可设宽高”的能力,但它依然没有固有尺寸、依然会响应 ::before/::after、依然不会有”加载外部资源”这件事。
这两者是完全不同维度的概念:一个是盒子的显示类型(display),一个是内容的来源方式(replaced / non-replaced)。
五、实际写代码时该注意什么
结合上面的原理,平时开发中几个实用结论:
- 想让图片保持比例又不变形,用
object-fit: cover(或contain),而不是简单粗暴地写死宽高。 - 图片底部莫名的空隙,加
display: block是最干净的解法,本质是让它脱离行内排版的基线对齐逻辑。 - 别指望在
img、input、video上用::before/::after做装饰,老老实实包一层容器。 - 自定义表单控件外观(比如好看的 checkbox、select)之所以麻烦,根源就是它们是替换元素,浏览器对它们内部渲染的控制权有限,这也是为什么现在流行”隐藏原生控件 + 用 div 从头画一个”的做法。
小结
Replaced element 这个概念,说到底解决的是一个问题:CSS 的排版系统是为文字设计的,但网页上从来都不只有文字。图片、视频、表单控件这些”非文字内容”需要一套不同的规则来描述它们在页面里的行为,于是就有了替换元素这个分类。
理解了这一层,很多平时”记住就好”的 CSS 规则(为什么 img 能设宽高、为什么图片底部有缝、为什么伪元素加不上去)背后的原因就都说得通了——它们不是孤立的规则,而是同一个底层逻辑的不同表现。