:focus-visible比:focus更适合做光圈效果,因其仅在Tab/Enter等键盘导航时触发,避免鼠标点击时突兀亮起;需处理outline覆盖、浏览器兼容及自定义元素的tabindex/role/事件监听三件事。

直接说结论:用 :focus-visible 替代 :focus 实现光圈效果,能避免鼠标点击时突兀亮起,但必须处理好 outline 覆盖、浏览器兼容和自定义元素支持这三件事。
为什么:focus-visible比:focus更适合做光圈效果
因为 :focus 一获得焦点就触发——鼠标点一下、JS 调用 input.focus()、甚至触摸屏轻点都会激活它,导致光圈在用户完全没按键盘时“啪”地弹出来,干扰视觉。而 :focus-visible 是浏览器自己判断的:只有 Tab、Shift+Tab、Enter、Space 这类真实键盘导航行为才会激活它,这才是“需要可见焦点提示”的场景。
光圈效果(比如 box-shadow: 0 0 8px 4px rgba(0, 120, 255, 0.3))本质是给键盘用户一个清晰的视觉锚点,不是给所有聚焦行为加戏。
光圈效果写法里最容易被 outline 干掉的坑
几乎所有浏览器默认给聚焦元素加一层 outline,它渲染层级高于 box-shadow,结果就是你写了再漂亮的发光,全被那层蓝边盖住——不是没生效,是看不见。
立即学习“前端免费学习笔记(深入)”;
- 必须显式重置:用
outline: 2px solid transparent+outline-offset: 2px,既保留可访问性所需的焦点轮廓占位,又不遮挡光圈 - 千万别写
outline: none,尤其别在全局规则如* { outline: none }里用,这会直接废掉:focus-visible的基础能力 - 光圈用
box-shadow时,第3、4个参数(模糊半径、扩散半径)建议 ≥4px,例如box-shadow: 0 0 12px 6px rgba(0, 120, 255, 0.25)
旧浏览器和自定义元素怎么兜底
:focus-visible 在 Safari 15.4+、Chrome 86+、Firefox 83+ 原生支持,但 Safari 15.3 及更早版本会直接忽略整条规则。更麻烦的是,div[tabindex="0"]、span[role="button"] 这类自定义可聚焦元素,默认不触发 :focus-visible,除非同时满足:
- 有
tabindex(哪怕为-1) - 绑了
keydown监听(至少响应 Enter/Space) - 加了语义化
role(如role="button"),否则屏幕阅读器不认
兜底写法要按顺序:input:focus 先清掉默认样式 → input:focus:not(:focus-visible) 显式关掉鼠标触发的样式 → input:focus-visible 单独加光圈。这样老浏览器只执行前两条,新浏览器才走第三条。
React/Vue 项目里容易漏掉的细节
组件库封装的输入框(比如 <Input />)常把原生 input 包在 div 里,如果没透传 tabIndex 或没转发 onKeyDown,:focus-visible 就不会冒泡到外层容器上。
- CSS-in-JS(如 styled-components)要显式写成
&:focus-visible,不能依赖自动扩展 - 别用 ID 选择器(如
#search-input:focus-visible),复用和测试都难 - 调试时直接在 DevTools 里勾选
:focus-visible状态,比反复切键盘快得多
真正麻烦的从来不是写那几行 CSS,而是确认 outline 没被重置、tabindex 没丢、role 和事件监听都到位——少一个,光圈就在该亮时不亮,不该亮时乱亮。


















