transform: scaleX()比width动画更干净,因其不触发重排、仅作用于合成层且支持transform-origin控制生长方向;而width强制重排易卡顿,且易因origin未重置或box-sizing导致模糊、抖动、偏移。

为什么 transform: scaleX() 比 width 动画更干净
因为 scaleX() 不触发重排(reflow),只影响合成层,动画更顺滑;而用 width 从 0 到 100% 会强制浏览器反复计算布局,尤其在低性能设备上容易卡顿。另外,scaleX() 天然支持 transform-origin 控制生长方向,比如从左、居中或右开始伸展。
常见错误是直接对 ::after 元素设 width: 0 → width: 100%,结果发现边缘模糊、缩放抖动,或聚焦后线条偏移——本质是没重置 transform-origin 和忽略默认 box-sizing 影响。
- 下划线必须用伪元素(
::after)并设position: absolute,脱离文本流 -
transform-origin必须显式设为left bottom(默认是center center,会导致缩放中心错位) - 父容器(
input)需设position: relative,否则::after会相对 body 定位 - 避免给
::after设left: 0+width: 100%,应统一用transform: scaleX(0)起手
如何让下划线只在 focus 时从左向右伸展
核心是控制伪元素的初始状态和过渡时机:初始 scaleX(0),聚焦时变为 scaleX(1),配合 transform-origin: left bottom 实现左起点生长。
input {
position: relative;
padding-bottom: 8px;
border: none;
outline: none;
}
input::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 2px;
background: #333;
transform: scaleX(0);
transform-origin: left bottom;
transition: transform 0.25s ease;
}
input:focus::after {
transform: scaleX(1);
}
注意:如果输入框有内边距或字体大小变化,bottom: 0 可能不准——此时建议改用 bottom: 4px 这类固定像素值,比百分比或依赖行高的方式更可控。
立即学习“前端免费学习笔记(深入)”;
hover 时提前预热下划线,但不干扰 focus 动画
很多人加 :hover::after 后发现 focus 状态失效,其实是 CSS 优先级或过渡属性冲突。关键是 hover 阶段只做「预热」,不覆盖 focus 的 transform 值,且保持同一套 transition 声明。
- 把
transition写在input::after基础选择器里,而非单独写在:focus::after中 -
:hover::after只设transform: scaleX(0.3),留出余量,避免和 focus 的scaleX(1)冲突 - 若要 hover 时不触发动画(仅 focus 才动),就删掉
:hover::after,或把 hover 的transition-delay设为0.1s避免误触
另一个坑是移动端没有 hover,所以纯靠 :focus 更稳妥;如需兼容,可额外加 :focus-within 或 JS 监听 touchstart 补充 class。
移动端点击区域小导致 focus 失败怎么办
原生 input 在 iOS 上点击热区窄,常点不中,导致下划线不触发。这不是 CSS 问题,而是 HTML 层缺失可点击包裹层。
解决方法不是放大 input 本身(会破坏极简感),而是用 label 包裹,并设 display: block + 合理 padding:
<label style="display: block; padding: 12px 0;"> <input type="text" placeholder="Email"> </label>
这样点击 label 任意位置都会触发 input focus,且不增加视觉元素。如果用了框架(如 React),确保 label 的 htmlFor 和 input 的 id 匹配,否则无效。
真正难调的点不在动画逻辑,而在不同浏览器对 transform-origin 的解析差异——Safari 有时会把 bottom 解析成基线(baseline)而非边界,这时换用 transform-origin: left 100% 更稳。


















