纯CSS可实现固定宽度导航的下划线缩放动画,但宽度不一致时需JS动态计算left和width;伪元素方案须设position:relative、content:""、transform-origin:left;响应式与路由切换需监听resize及同步active状态。

纯 CSS 实现的横向导航下划线动画,只要结构和定位写对,根本不需要 JS 就能跑起来;但一旦想让下划线“跟随悬停项平滑移动”,就必须用 JS 动态计算位置——这是绝大多数人卡住的第一道坎。
伪元素 ::after 实现左→右滑入动画
本质是用 ::after 画一条细线,靠 transform: scaleX() 控制伸缩,性能好、无重排。但有三个硬性前提:
-
a标签必须设position: relative,否则::after的绝对定位会脱标 -
::after必须有content: "",否则不渲染(常见静默失败) - 初始状态要加
transform-origin: left,否则scaleX(0)会从中心收缩,视觉错位
示例关键样式:
nav a {
position: relative;
}
nav a::after {
content: "";
position: absolute;
bottom: -4px;
left: 0;
width: 100%;
height: 2px;
background: #007bff;
transform: scaleX(0);
transform-origin: left;
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
nav a:hover::after {
transform: scaleX(1);
}
用 width + left 实现下划线“跟随”悬停项
当菜单项宽度不一致(比如「首页」vs「关于我们」),仅靠 scaleX 无法位移,必须同时控制水平位置。此时伪元素方案失效,得用真实 DOM 节点(如 <span class="underline"></span>)并配合 JS。
立即学习“前端免费学习笔记(深入)”;
核心逻辑是:鼠标移入时,读取目标 a 的 getBoundingClientRect(),设置下划线的 left 和 width,再触发 CSS 过渡。
- 不要用
offsetLeft—— 它受父级padding或transform影响,容易偏移 -
transition必须同时写left 0.3s, width 0.3s,只写一个会导致“拉伸但不动”或“移动但不伸展” - 首次加载后需手动初始化下划线位置,否则页面刚打开时它可能在左上角闪一下
响应式下划线错位的修复时机
窗口缩放(resize)后,所有导航项尺寸都变了,但 JS 缓存的坐标没更新,下划线就会漂移。这不是 bug,是漏监听。
- 必须在
window.addEventListener('resize', updateUnderlinePosition)中重新测量每个a的getBoundingClientRect() - 别用防抖(debounce)过度——缩放到一半就暂停测量,下划线会卡在旧位置;建议节流到 100ms 内执行一次
- 移动端横竖屏切换也触发
resize,同样要覆盖
点击后保持高亮状态的 class 切换逻辑
:active 只在鼠标按下瞬间生效,:hover 离开就消失,都不适合“当前页高亮”。必须用 JS 绑定点击事件,手动增删 class。
- 点击某项时,先清除所有
.active,再给当前a加.active - CSS 中定义
.active::after或.active .underline的初始状态(如transform: scaleX(1)),确保它和 hover 效果一致 - 如果用 history.pushState 或路由跳转,记得在路由钩子中同步更新 active 状态,否则刷新页面后高亮丢失
最易被忽略的是:多个动画属性(transform、left、width)混用时,transform 优先级高于 left,若同时存在且未协调,下划线会“不动”或“乱跳”。动手前先理清你到底需要位移+缩放,还是只缩放——选错方案,后面全白调。



















