CSS 中的 scale() 往往会把文字推入 GPU 合成层,通过双线性插值对纹理进行缩放,从而绕过字体的亚像素光栅化机制,最终让文字边缘变得模糊发虚;想确认这一点,可以在 Chrome 的 Layers 面板中查看是否出现红色图层边框。

本质原因在于:scale() 会把文本从主渲染层带入 GPU 合成层,再以双线性插值方式缩放纹理,导致字体无法继续使用亚像素光栅化,所以看起来会发虚、不够清晰。
scale() 触发的是 GPU 纹理缩放,本质上不是“性能加速”,而是文字渲染降级
浏览器实现 transform: scale() 时,通常会先把元素内容(包括文本)光栅化为位图,然后交给 GPU 作为纹理进行缩放处理。GPU 默认采用双线性滤波插值,这对图片缩放很常见,但对于文字来说,会损失边缘原本清晰的亚像素细节。因此你看到的文字模糊,并不是某个“清晰度开关”没打开,而是整个渲染流程已经避开了字体精细光栅化路径。
验证方法很直接:打开 Chrome DevTools → Layers 面板,将鼠标悬停到目标文字区域,如果出现红色图层框,就说明该元素已经脱离主图层,当前正在由 GPU 进行纹理插值渲染。
- 普通文字(未使用 transform):通常走亚像素抗锯齿渲染,例如 macOS 下的
-webkit-font-smoothing: subpixel-antialiased - 应用了
scale(1.2)的文字:常会降级为灰度抗锯齿,甚至直接失去更细腻的字体边缘处理,因此更容易发糊 scale(1)或scale(2)一般不容易模糊——整数倍缩放通常不会明显触发插值误差
translateZ(0) 不是解决方案,某些情况下还会让文字更模糊
translateZ(0) 的作用只是强制创建新的合成层,它并不能修复像素对齐,也不会让浏览器重新走字体光栅化流程。在 Windows Chrome 110+ 某些场景下,它可能带来轻微改善,但前提通常是要与 scale() 写在同一条 transform 中,例如 transform: scale(1.1) translateZ(0);如果只是单独加上,通常没有实际效果。
- 在 macOS Safari 以及大多数移动端浏览器中,
translateZ(0)基本不会改善文字清晰度问题 - 如果父容器已经设置了
will-change: transform,继续叠加translateZ(0)可能造成图层嵌套,反而让模糊更明显 - 在长列表、卡片瀑布流等高密度场景里滥用这类写法,还会迅速增加内存占用和合成开销
更可靠的优化方案:尽量避开 GPU 插值渲染路径
真正有效的思路,不是依赖所谓“硬件加速”,而是避免触发文字渲染降级。关键点不在于如何把元素放大,而在于尽量让浏览器始终以主图层方式渲染文本内容。
- 优先使用
font-size配合transition: font-size来替代scale()—— 虽然性能可能略逊,但文字清晰度通常更稳定 - 如果必须使用
scale(),尽量保证缩放后的字号落在整数像素上,例如font-size: 16px搭配scale(1.25)得到 20px,比scale(1.23)得到 19.68px 更不容易发虚 - 对于位移动画,尽量使用整数像素值:
transform: translateX(10px)通常比translateX(50%)更安全;如果必须用百分比,要先确认容器尺寸不会产生 49.5px 这类小数像素 - 可以给父容器添加
isolation: isolate,避免它意外创建合成层,进而把内部子元素文字一并拖入 GPU 渲染路径
最容易被忽视的一点是:CSS transition 中文字发虚,往往并不是某一个单独属性造成的,而是 scale()、小数像素位移以及父容器 will-change 叠加后的综合结果。通常只要去掉其中任意一个诱因,模糊问题就可能明显缓解;如果只去调整 font-smoothing,基本等于只做表面处理,无法解决根本原因。
