你是否曾希望页面上的文字在鼠标悬停时能够平滑变大,并为此写下了 transition: font-size 0.3s,结果却发现——文字“啪”地一下直接跳变,完全没有过渡动画?其实,并非你的代码有误,问题的根源在于浏览器本身:它根本不会对 font-size 进行插值计算,尤其是在单位混用、包含 0 或继承值(如 inherit、unset)的情况下,会直接跳过过渡过程。

为什么 font-size transition 经常失效
浏览器只对“能够明确计算出中间值”的属性执行过渡动画。而 font-size 在以下场景中,恰恰无法完成插值计算:
- 起始或结束值使用了不同单位,例如从
16px过渡到1.2em,浏览器无法在两者之间找到平滑的运算路径。 - 其中一端是
0、auto、initial、inherit这类特殊值,插值计算链会直接断裂。 - 父元素的
font-size也在动态变化,而子元素使用em单位时,计算链条变得复杂且不稳定。 - 元素本身没有显式声明初始
font-size,完全依赖继承而来。transition 找不到起点,自然无法“动起来”。
真正能用的三种方案(按推荐顺序)
首选方案:用 transform: scale() 模拟字号变化
这是最稳妥的路径。它走 GPU 合成层,不触发重排,所有现代浏览器都能平滑支持。不过,有几个细节需要留意:
- 必须给元素加上
display: inline-block或block,否则transform-origin无法生效。 - 将锚点设置为
transform-origin: top left,可以避免文字上跳,特别适合标题、按钮等不牵连整体布局的场景。 - 千万不要同时修改
font-size和scale(),否则文字会出现模糊问题。
标准示例:
.text {
display: inline-block;
transform-origin: top left;
transition: transform 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.text:hover {
transform: scale(1.25);
}
次选方案:CSS 自定义属性 + calc()
如果你必须改变真实的 font-size,并且希望享受 CSS 的流畅过渡,可以考虑使用 @property 注册自定义属性。具体做法是:定义一个 --fs-multiplier,然后写 font-size: calc(var(--fs-multiplier) * 1rem)。但关键一步是,必须用 @property 将该变量注册为可动画类型,否则 transition 依然无效。这个方案对浏览器版本有要求(Chromium 110+ / Firefox 111+),适合响应式字号渐变,例如随视口宽度连续缩放。
兜底方案:JavaScript 手动逐帧更新
如果你需要兼容老浏览器,或者必须精确控制动画,可以考虑用 requestAnimationFrame 逐帧更新 element.style.fontSize。但要注意,避免直接循环修改 style.fontSize——这会强制同步布局,导致严重掉帧。仅在必须精确控制或需配合其他逻辑(如 scroll 触发)时使用。
哪些写法看着像对,其实踩坑
这些常见做法看似合理,实际并不可靠:
transition: all 0.3s—— 会意外过渡margin、border等属性,引发布局抖动,得不偿失。- 从
font-size: 0开始过渡 —— 几乎所有浏览器都不支持0到非零值的插值计算。 - 使用
em单位,但父级字号也在变 —— 子元素过渡不同步,视觉上会出现卡顿。 - 给
display: none元素加 transition —— 完全不触发,因为元素已脱离渲染流。
真正需要想清楚的是:你要动的是“排版尺寸”(影响布局、需真实改变 font-size),还是“视觉尺寸”(只求看起来变大,用 transform 更稳)。前者在实际项目中极少需要动画,后者才是日常高频需求。理解了这一点,技术方案的选择就清晰多了。
