先记住这个答案
:hover 和 :focus 都是动态伪类,不改变 DOM 结构,只根据用户交互状态实时匹配。:hover 在鼠标指针进入元素区域时生效,移出后消失。:focus 在元素获得焦点时生效,通过点击、Tab 键或 JavaScript 的 focus() 方法都能触发。两个可以同时生效。
:hover鼠标悬停时生效:focus获得焦点时生效- 触屏设备上
:hover会粘住 :focus-visible只在键盘聚焦时显示
链接伪类的书写顺序
给 <a> 标签写样式时,:hover 和 :focus 的顺序很重要。如果顺序不对,某些状态的样式会被后面的覆盖掉,看起来像是不生效。业界通用的记忆口诀是 LVHFA。
a:link { color: blue; } /* 未访问 */
a:visited { color: purple; } /* 已访问 */
a:hover { color: red; } /* 鼠标悬停 */
a:focus { outline: 2px solid; } /* 键盘聚焦 */
a:active { color: orange; } /* 正在点击 */这个顺序之所以重要,是因为这些伪类的优先级相同(都是一个伪类的权重)。当多个状态同时成立时,比如鼠标悬停在一个已访问的链接上,:hover 和 :visited 都匹配。因为优先级相同,后写的赢,所以 :hover 必须在 :visited 后面才能生效。
:active 放最后是因为点击的一瞬间 :hover 和 :active 同时成立,:active 的样式要盖过 :hover。
触屏设备上 hover 会粘住
在手机和平板上,没有鼠标悬停这个动作。用户点击一个元素时,浏览器会模拟一次 :hover,但问题是这个 :hover 不会自动消失,会一直「粘」在那个元素上,直到用户点击了其他地方。
这会导致一个常见的 bug:把重要的交互功能(比如下拉菜单的显示)放在 :hover 上,在手机上就变成了点一下显示、点别处才消失。更好的做法是把 :hover 仅用于视觉增强(比如变色、加阴影),关键交互用 JavaScript 的 click 事件处理。
/* 只在有悬停能力的设备上生效 */
@media (hover: hover) {
.card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
}
}@media (hover: hover) 检测设备是否支持真正的悬停操作。触屏设备上这个条件不成立,:hover 样式就不会生效,避免了粘滞问题。
focus-visible:只在键盘操作时显示焦点样式
:focus 有个烦人的问题:鼠标点击按钮时也会触发 :focus,按钮周围出现一个焦点轮廓。很多设计师觉得这个轮廓不好看,想去掉。但如果直接写 outline: none,键盘用户就看不到焦点在哪了,这是严重的可访问性问题。
:focus-visible 解决了这个矛盾。它只在浏览器认为「用户需要看到焦点指示」时才匹配,通常是键盘操作触发的焦点。鼠标点击不会触发 :focus-visible,所以可以放心地在 :focus-visible 上加轮廓样式,不用担心鼠标用户看到多余的边框。
容易答错的地方
- 用 hover 做核心交互而不是增强
- 在手机上
:hover行为不可靠。如果下拉菜单的展开完全依赖:hover,手机用户要么无法使用,要么体验很差。:hover应该只做视觉增强,核心交互用click或:focus。 - 去掉 focus 样式却不提供替代
button:focus { outline: none; }这样写会让键盘用户完全看不到焦点在哪个元素上。正确的做法是保留:focus-visible的轮廓,只去掉鼠标点击时的样式。
面试官还会怎么问?
hover 能用在手机端吗?
可以,但行为不一样。手机浏览器会在用户点击时模拟 :hover,而且会「粘住」直到点击别处。所以用 @media (hover: hover) 限制只在真正支持悬停的设备上生效,是更稳妥的做法。
focus 和 focus-within 有什么区别?
:focus 只匹配获得焦点的那个元素本身。:focus-within 匹配的是获得焦点的元素以及它的所有祖先。比如输入框获得焦点时,:focus-within 可以让包裹它的表单组容器也变色。
怎么让一个不可聚焦的元素支持 focus?
HTML 里默认能接收焦点的只有 <input>、<button>、<a> 等交互元素。给其他元素加上 tabindex="0" 就能让它通过 Tab 键获得焦点,:focus 和 :focus-visible 也就能生效了。tabindex="-1" 只能通过 JavaScript 聚焦,不能通过 Tab。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。