CSS 鼠标滑过 hover 效果怎么写?防抖动、触屏隔离与无障碍
鼠标“滑过效果”(hover)是前端最容易写出廉价感的地方:有的元素一 hover 就抖、有的在触屏设备上完全失效、有的让整页掉帧。本文把 hover 的实现要点、性能写法与触屏/键盘替代方案整理清楚。

一、纯 CSS 是首选
绝大多数 hover 不需要 JS:
/* 1. 卡片浮起:用 transform + box-shadow,不动边框 */
.card { transition: transform .22s ease, box-shadow .22s ease; }
.card:hover { transform: translateY(-3px);
box-shadow: 0 8px 24px rgba(0,0,0,.12); }
/* 2. 图片缩放:容器必须 overflow:hidden */
.thumb { overflow:hidden; }
.thumb img { transition: transform .35s ease; }
.thumb:hover img { transform: scale(1.06); }
/* 3. 遮罩淡入:用 opacity,不要用 display 切换 */
.mask { opacity:0; transition: opacity .25s ease; }
.card:hover .mask { opacity:1; }
/* 4. 下划线从左展开 */
.link { position:relative; }
.link::after { content:‘’; position:absolute; left:0; bottom:-2px; height:2px; width:100%;
background:currentColor; transform:scaleX(0); transform-origin:left;
transition:transform .25s ease; }
.link:hover::after { transform:scaleX(1); }
二、“抖一下”的四个典型原因
- 改了边框或尺寸:
border:2px → 3px会挤动周围布局。改用box-shadow或outline模拟加粗; - hover 改变了触发区域:元素被推开后鼠标脱离热区,恢复原态又被悬停,形成抖动循环。解法是只在父容器监听,或用不影响布局的
transform; - transition 只写在伪类上:移出时没有动画。基础选择器就要写 transition;
- font-weight 让文字变宽:加粗导致重排。可用文字描边、或预留等宽占位。
三、必须处理触屏与键盘
手机没有 hover,用媒体查询隔离,避免“点了才出现、再点消失”:
@media (hover: hover) and (pointer: fine) {
.card:hover .mask { opacity:1 }
}
@media (hover: none) {
.mask { opacity:1 } /* 触屏下改为常显,或改成点击展开 */
}
键盘可达性:焦点态要与悬停一致,写 .card:hover, .card:focus-within { ... }。只用 hover 的下拉菜单,键盘用户完全无法打开。
四、下拉菜单的 hover 断裂问题
.nav > li { position:relative }
.nav .sub { position:absolute; top:100%; left:0; opacity:0; visibility:hidden;
transform:translateY(6px); transition:opacity .18s, transform .18s }
.nav > li:hover .sub, .nav > li:focus-within .sub { opacity:1; visibility:visible; transform:none }
/* 关键:用透明伪元素补齐父子项之间的空隙 */
.nav .sub::before { content:''; position:absolute; top:-10px; left:0; right:0; height:10px }
鼠标从一级项移到二级面板之间有几像素空白,会让 hover 判定断开;补一个透明接触区即可,不必用 JS 延时。
五、性能与无障碍
- 动画只走
transform / opacity,避免width/height/top/left/background-position(触发重排重绘); - 时长 120~250ms 最舒适,超过 300ms 显得迟钝;
- 尊重系统偏好:
@media (prefers-reduced-motion: reduce) { * { transition-duration:.01ms !important; animation-duration:.01ms !important } } - 不要把整页元素都加动画,划过长列表时同时重绘会明显掉帧;
- hover 里不要改变内容高度(展开文字会让下方跳动),改用绝对定位层或点击展开。
六、什么时候才需要 JS
- 跟随鼠标位置(光斑/磁吸):写 CSS 变量而不是直接改样式;
btn.addEventListener('pointermove', e => { const r = btn.getBoundingClientRect(); btn.style.setProperty('--x', (e.clientX - r.left) + 'px'); btn.style.setProperty('--y', (e.clientY - r.top) + 'px'); }); - hover 延时(防误触/预加载):用
pointerenter/pointerleave+setTimeout,离开时务必clearTimeout,请求延时 200~300ms 并做去重缓存。
七、常见问题 FAQ
问:iframe 里 hover 为什么没反应?
答:鼠标移出 iframe 边界不触发父文档 hover,父文档也无法跨文档控制。解法是合并到同一文档或用 postMessage 通知。
问:hover 时出现滚动条导致页面横向跳动?
答:给根元素加 scrollbar-gutter: stable,或始终预留滚动条空间。
问:hover 换图先闪一下白?
答:第二张图未预加载。让两张图都在 DOM 里(叠放 + opacity 切换),或使用雪碧/合并图。
总结:hover 用 CSS;动画只走 transform/opacity;用 @media (hover:hover) 隔离触屏;用 :focus-within 兼顾键盘;抖动多半是改了布局属性。
生产内容,同时打通搜索引擎(SEO)与AI搜索引擎(GEO)流量入口的软件! 点击进入365建站器 »
