模糊是浏览器将元素绘制在物理像素中间触发插值所致;有效解法是让transform值对齐设备物理像素,而非依赖will-change等无效手段。

直接结论:模糊不是 bug,是浏览器把元素画在了物理像素中间位置,触发插值渲染的结果;真正有效的解法是让 transform 值对齐设备物理像素,而不是加 will-change、translateZ(0) 或调 font-smoothing。
为什么 translate(-50%, -50%) 在移动端特别容易模糊
因为 50% 是按父容器当前尺寸算的,而移动端视口宽度常为奇数(如 375px),375 * 0.5 = 187.5px —— 这个半像素位移会直接落到 DPR=2 设备的 375 物理像素中间,强制插值。
- 不要依赖百分比做 transform 起点,改用
left: 50%; top: 50%; transform: translate(-50%, -50%),并确保getBoundingClientRect().left是整数 - 检查父容器宽高是否为偶数,奇数尺寸 + 百分比位移 = 高概率半像素
- 安卓 WebView 和旧版 Chrome 对 sub-pixel 渲染支持极弱,
187.5px可能被直接截断成187px或188px,造成抖动+模糊叠加
JS 动态校准 transform 值必须做的三件事
硬写 translateX(100px) 没用,关键是要让这个 100px 真正落在整数物理像素上。
- 用
Math.round(value * window.devicePixelRatio) / window.devicePixelRatio规整,例如:要移动元素宽度的 50%,先取el.offsetWidth * 0.5,再套公式 - 监听
window.visualViewport?.scale和window.devicePixelRatio变化,在缩放或横竖屏切换后重算 -
transform: translateZ(0)不解决模糊,它只创建合成层;但transform: translate(0, 0)有时能重触发光栅化,可作为轻量兜底
伪元素画线方案里最容易漏掉的四个条件
用 ::after + transform: scaleY(0.5) 是目前最稳的 1px 边框方案,但漏任一条件就会失效。
- 父元素必须设
position: relative,否则bottom: 0没参照系 - 伪元素必须设
height: 1px(不能用border-bottom: 1px),否则缩放仍走插值路径 -
transform-origin: left bottom必须和bottom: 0配合,否则scaleY(0.5)从顶部压,留出半像素空隙 -
pointer-events: none缺失会导致伪元素遮挡点击,表单内 focus 失效很常见
真正难处理的是那些“看起来没动、其实一直在微调”的场景:比如地图拖拽时的 marker、滚动中实时计算的 tooltip、DPR 动态变化的折叠面板 —— 这些地方的 transform 值必须每一帧都校准,不能只 onload 算一次。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











