SSR 中 autofocus 仅在首次 HTML 解析时生效,客户端动态插入或 hydration 后新增的 autofocus 均被浏览器忽略;需手动 focus() 并规避 SSR、可见性、iOS 手势等陷阱。

服务端渲染(SSR)中写入的 autofocus 属性会生效,但客户端动态插入或 hydrate 后新增的同名元素,无论是否带该属性,一律被浏览器忽略——这不是 bug,是规范强制行为。
SSR 输出的 autofocus 为什么只“生效一次”
浏览器只在 HTML 字符串首次解析阶段扫描 autofocus,而 SSR 输出正是这个“首次解析”的来源。哪怕你在客户端用 innerHTML 插入一个带 autofocus 的 <input>,它已经处于 DOM 构建完成后的运行时阶段,浏览器不会再重新评估任何 autofocus 属性。
- 常见现象:SSR 返回了
<input name="search" autofocus>,页面加载后焦点确实在搜索框;但用户点击按钮打开登录弹窗,里面也有<input autofocus>,却完全没反应 - 更隐蔽的问题:Vue/React hydration 时复用了 SSR 渲染的 DOM 节点,但后续通过
v-if或useState新增的弹窗节点,其autofocus属性只是字符串,不会触发聚焦 -
document.querySelector('[autofocus]')能查到元素,不代表它会被聚焦——浏览器早已“盖章作废”
客户端 hydrate 后手动 focus() 必须绕开三个陷阱
不能直接在 onMounted 或 useEffect 里无条件调 element.focus(),否则在 SSR 场景下极易报错或静默失败。
-
if (typeof window === 'undefined')必须包裹所有 DOM 操作,否则 SSR 期间执行会抛ReferenceError: document is not defined - 元素可能尚未真正可见:CSS transition 动画未结束、父容器
opacity: 0或visibility: hidden,此时focus()静默拒绝,需用getComputedStyle(element).visibility === 'visible'和element.offsetParent !== null双重校验 - iOS Safari 对非用户手势触发的聚焦极其严格,
useEffect(() => ref.current?.focus(), [])在它眼里等同于“自动”,基本无效;必须把focus()绑定在用户点击、回车等事件回调里
多个弹窗共存时如何避免焦点抢夺
SSR 输出里若预置了多个带 autofocus 的弹窗模板(比如首页同时渲染了搜索框 + 订阅表单 + 登录浮层),浏览器只会按源码顺序对第一个可聚焦元素生效,其余全部丢弃——且这个“第一个”和你当前看到的视觉层级无关。
立即学习“前端免费学习笔记(深入)”;
- 解决方案不是“删掉 SSR 的 autofocus”,而是“统一删除所有 HTML 中的
autofocus”,改用 JS 控制,并为每个弹窗维护独立的焦点逻辑 - 弹窗打开后,先判断
document.activeElement是否已在目标元素内,再决定是否聚焦,避免覆盖用户正在操作的输入框 - 若弹窗支持键盘关闭(如 Esc),关闭后应显式调用
document.body.focus()或element.blur(),防止焦点残留导致 Tab 键跳转异常
最常被忽略的一点:焦点状态 ≠ 可编辑状态。即使 input.focus() 成功执行,在 iOS Safari 上仍可能不弹软键盘——除非它发生在用户手势同步上下文中,且元素已真实进入视口、未被 CSS 隐藏、未被 inert 锁定。



















