CSS 用户行为伪类::hover 与 :focus 详解

1. 什么是用户行为伪类

在 CSS 中,伪类(Pseudo-class)用于描述元素的特定状态 。其中一类伪类专门用来表达「用户与元素发生了交互」,我们称之为用户行为伪类(User Action Pseudo-classes)。

最常见的两个就是:

  • :hover:鼠标指针悬停在元素上时触发
  • :focus:元素获得焦点时触发(如点击输入框、Tab 键切换)

它们让页面从「静态展示」变成「可感知的交互界面」,是提升可用性与视觉反馈的基础手段。

2. :hover ------ 鼠标悬停

2.1 基本用法

:hover 在鼠标指针进入元素区域时生效,移出后失效。

css 复制代码
.button {
  background-color: #1e88e5;
  color: #fff;
  transition: background-color 0.2s ease;
}

.button:hover {
  background-color: #1565c0;
}

上面的例子中,鼠标移入按钮时背景色变深,配合 transition 得到平滑过渡。

2.2 常见应用场景

  • 导航菜单项高亮
  • 表格行悬停高亮,方便横向阅读
  • 卡片阴影加深,营造「浮起」效果
  • 图片轻微放大或显示遮罩层
css 复制代码
.table-row:hover {
  background-color: #f5f7fa;
}

.card {
  transition: box-shadow 0.25s ease, transform 0.25s ease;
}

.card:hover {
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
  transform: translateY(-2px);
}

2.3 注意事项

  • 移动端没有真正的 hover 。触摸设备上 :hover 的行为不可靠,可能表现为「点击后一直保持悬停态」。因此不要把关键信息或功能只放在 :hover 里。
  • 建议用 @media (hover: hover) 包裹悬停样式,只在支持悬停的设备上生效:
css 复制代码
@media (hover: hover) and (pointer: fine) {
  .card:hover {
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
  }
}
  • :hover 会沿 DOM 树向上冒泡影响祖先元素,注意选择器范围,避免误伤。

3. :focus ------ 获得焦点

3.1 基本用法

:focus 在元素成为当前焦点时生效,典型场景是表单控件被点击或通过 Tab 键选中。

css 复制代码
.input {
  border: 1px solid #ccc;
  outline: none;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.input:focus {
  border-color: #1e88e5;
  box-shadow: 0 0 0 3px rgba(30, 136, 229, 0.2);
}

3.2 可聚焦元素

并非所有元素都能获得焦点。默认可聚焦的元素包括:

  • 表单控件:input、select、textarea、button
  • 链接:a[href]
  • 带 tabindex 属性的元素(tabindex="0" 或负值)

如果想让普通 div 也能聚焦,需要显式添加 tabindex:

html 复制代码
<div class="card" tabindex="0">可聚焦的卡片</div>

3.3 不要随意移除焦点轮廓

很多开发者习惯写 outline: none 去掉默认焦点框,但这会严重损害键盘用户的可访问性------他们无法判断当前焦点在哪。

正确做法是替换 而不是删除:

css 复制代码
/* 不推荐 */
.input:focus {
  outline: none;
}

/* 推荐:用更清晰的样式替代 */
.input:focus-visible {
  outline: 2px solid #1e88e5;
  outline-offset: 2px;
}

4. :focus-visible 与 :focus-within

4.1 :focus-visible

:focus-visible 只在浏览器判断「用户需要看到焦点指示」时生效。典型表现是:

  • 鼠标点击按钮 → 通常不 触发 :focus-visible
  • 键盘 Tab 切换 → 触发 :focus-visible

这样既能给键盘用户清晰的焦点提示,又不会在鼠标点击时出现多余的轮廓。

css 复制代码
.button:focus-visible {
  outline: 2px solid #1565c0;
  outline-offset: 2px;
}

4.2 :focus-within

:focus-within 在元素自身或其后代获得焦点时生效,非常适合做表单容器的整体高亮。

css 复制代码
.form-group:focus-within {
  border-color: #1e88e5;
  background-color: #f5f9ff;
}
html 复制代码
<div class="form-group">
  <label for="email">邮箱</label>
  <input id="email" type="email" />
</div>

当输入框获得焦点时,整个 .form-group 都会高亮,视觉上更完整。

5. 组合使用与优先级

多个伪类可以叠加,表示「同时满足」:

css 复制代码
/* 鼠标悬停且获得焦点 */
.button:hover:focus {
  background-color: #0d47a1;
}

/* 悬停时,若内部有元素获得焦点,则高亮 */
.card:hover:focus-within {
  border-color: #1e88e5;
}

需要注意 CSS 优先级(Specificity):伪类与类选择器权重相同,均为 0,1,0。因此 :hover 与 :focus 叠加时权重会累加,写样式时留意覆盖顺序。

6. 状态流转示意

#mermaid-svg-sp432c9iplFmD5QH{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-sp432c9iplFmD5QH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-sp432c9iplFmD5QH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-sp432c9iplFmD5QH .error-icon{fill:#552222;}#mermaid-svg-sp432c9iplFmD5QH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-sp432c9iplFmD5QH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-sp432c9iplFmD5QH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-sp432c9iplFmD5QH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-sp432c9iplFmD5QH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-sp432c9iplFmD5QH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-sp432c9iplFmD5QH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-sp432c9iplFmD5QH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-sp432c9iplFmD5QH .marker.cross{stroke:#333333;}#mermaid-svg-sp432c9iplFmD5QH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-sp432c9iplFmD5QH p{margin:0;}#mermaid-svg-sp432c9iplFmD5QH .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-sp432c9iplFmD5QH .cluster-label text{fill:#333;}#mermaid-svg-sp432c9iplFmD5QH .cluster-label span{color:#333;}#mermaid-svg-sp432c9iplFmD5QH .cluster-label span p{background-color:transparent;}#mermaid-svg-sp432c9iplFmD5QH .label text,#mermaid-svg-sp432c9iplFmD5QH span{fill:#333;color:#333;}#mermaid-svg-sp432c9iplFmD5QH .node rect,#mermaid-svg-sp432c9iplFmD5QH .node circle,#mermaid-svg-sp432c9iplFmD5QH .node ellipse,#mermaid-svg-sp432c9iplFmD5QH .node polygon,#mermaid-svg-sp432c9iplFmD5QH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-sp432c9iplFmD5QH .rough-node .label text,#mermaid-svg-sp432c9iplFmD5QH .node .label text,#mermaid-svg-sp432c9iplFmD5QH .image-shape .label,#mermaid-svg-sp432c9iplFmD5QH .icon-shape .label{text-anchor:middle;}#mermaid-svg-sp432c9iplFmD5QH .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-sp432c9iplFmD5QH .rough-node .label,#mermaid-svg-sp432c9iplFmD5QH .node .label,#mermaid-svg-sp432c9iplFmD5QH .image-shape .label,#mermaid-svg-sp432c9iplFmD5QH .icon-shape .label{text-align:center;}#mermaid-svg-sp432c9iplFmD5QH .node.clickable{cursor:pointer;}#mermaid-svg-sp432c9iplFmD5QH .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-sp432c9iplFmD5QH .arrowheadPath{fill:#333333;}#mermaid-svg-sp432c9iplFmD5QH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-sp432c9iplFmD5QH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-sp432c9iplFmD5QH .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-sp432c9iplFmD5QH .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-sp432c9iplFmD5QH .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-sp432c9iplFmD5QH .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-sp432c9iplFmD5QH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-sp432c9iplFmD5QH .cluster text{fill:#333;}#mermaid-svg-sp432c9iplFmD5QH .cluster span{color:#333;}#mermaid-svg-sp432c9iplFmD5QH div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-sp432c9iplFmD5QH .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-sp432c9iplFmD5QH rect.text{fill:none;stroke-width:0;}#mermaid-svg-sp432c9iplFmD5QH .icon-shape,#mermaid-svg-sp432c9iplFmD5QH .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-sp432c9iplFmD5QH .icon-shape p,#mermaid-svg-sp432c9iplFmD5QH .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-sp432c9iplFmD5QH .icon-shape .label rect,#mermaid-svg-sp432c9iplFmD5QH .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-sp432c9iplFmD5QH .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-sp432c9iplFmD5QH .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-sp432c9iplFmD5QH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 鼠标移入
鼠标移出
点击 / Tab 聚焦
失焦 blur
移入并聚焦
移出或失焦
默认状态
hover 状态
focus 状态
hover + focus

7. 可访问性最佳实践

  1. 永远不要用 outline: none 直接抹掉焦点样式 ,用 :focus-visible 提供更清晰的替代方案。
  2. 不要只依赖颜色变化表达状态,可配合边框、阴影、下划线等多种视觉线索。
  3. 保证对比度:悬停/聚焦态与默认态之间应有足够差异,同时文字与背景对比度满足 WCAG 要求。
  4. 移动端优先 :交互反馈不要只依赖 :hover,触摸设备上应通过 :active 或点击态提供反馈。
  5. 保持过渡自然 :给 transition 设置 150--250ms 的时长,避免过慢导致操作迟滞。

8. 小结

伪类 触发条件 典型用途
:hover 鼠标悬停 按钮高亮、卡片浮起、行高亮
:focus 元素获得焦点 输入框边框、按钮焦点态
:focus-visible 需要显示焦点指示时 键盘导航的焦点轮廓
:focus-within 自身或后代获得焦点 表单容器整体高亮

:hover 与 :focus 是构建交互反馈的基石。用好它们的关键在于:既要有明确的视觉反馈,又不能牺牲可访问性 。配合 :focus-visible、:focus-within 以及媒体查询,可以写出既美观又对键盘与触摸用户友好的样式。

相关推荐
深蓝AI17 小时前
Meta 开源用了八年的设计系统 astryx:连坐两天 GitHub 总榜第一,组件库开始写给 agent 的说明书
前端·人工智能
IT_陈寒18 小时前
Vue的watch把我坑惨了,原来要这样用才靠谱
前端·人工智能·后端
1-1=018 小时前
Vue 3 模板语
前端·javascript·vue.js
用户76103044737118 小时前
【转战智能眼镜开发】ROKID AIUI 小游戏 亚马逊标枪项目实战讲解
前端
风卿18 小时前
看着AI写代码太孤独了,来欣赏星云吧:一个DSH 装饰插件
前端·javascript
牧艺19 小时前
弹层不用再手算坐标了:CSS Anchor Positioning 从原理到可上线
前端·javascript·css
用户老李19 小时前
YOLO 目标检测自学教程 07:YOLOv3——多尺度预测
前端
moshuying19 小时前
ESP32 墨水屏金价牌(上):从点亮到把图片推过去
前端
李剑一19 小时前
别再抬杠,前端岗已经没了!无论前后端,我们最终都要成“全干”工程师
前端·面试
Highcharts.js19 小时前
Highcharts 仪表盘与速度仪图表库介绍
前端·信息可视化·仪表板·highcharts·图表开发·图表库·仪表可视化