移动端 focus 后光标被软键盘遮挡需先获取光标 Range 并判断是否被遮挡,再滚动父容器而非元素自身;iOS 需微任务后构造 Range、requestAnimationFrame 后计算位置;Android fallback 到 window.innerHeight + scrollTop;空内容时须检查 rangeCount > 0 或主动创建 Range。

移动端 focus 后光标被软键盘遮挡怎么办
直接调用 scrollIntoView 基本无效——contenteditable 元素不触发标准表单聚焦行为,iOS Safari 甚至不会在 focus 时更新 visualViewport.height,导致滚动时机错判。
核心不是“滚不滚”,而是“什么时候滚、滚谁”:
- 监听
focus事件,但不能只对contenteditable元素自身调scrollIntoView - 先用
getSelection().getRangeAt(0)获取当前光标位置的Range,再取其getBoundingClientRect().bottom值 - 对比
visualViewport?.height判断是否被遮挡(iOS 必须等微任务后构造 Range) - 若被遮挡,调用
range.commonAncestorContainer.parentElement.scrollIntoView({ block: 'nearest', inline: 'nearest' }),而非元素自身 - Android WebView 中
visualViewport支持弱,fallback 到window.innerHeight + document.documentElement.scrollTop计算视口底部
iOS 双击后光标总跳到开头怎么 fix
这不是 bug,是浏览器默认行为:contenteditable="true" 设置后 DOM 尚未完成重排,getSelection() 返回空或过期 Range,后续 focus() 只能 fallback 到首个文本节点起始处。
必须在设置属性后加一层微任务等待渲染:
立即学习“前端免费学习笔记(深入)”;
文章转信息图。将文章/笔记转化为手机可读的 HTML 信息图,自动匹配视觉风格。触发场景:文章转图、笔记转图、信息图、转小红书图、做张图、可视化这篇文章、文生图。
- 用
setTimeout(() => { /* 构造 Range */ }, 0)或监听transitionend(如果用了 CSS 过渡) - 定位光标前,优先用
document.caretRangeFromPoint(x, y)(Safari)或document.caretPositionFromPoint(x, y)(Chrome/Firefox)获取点击点对应的node和offset - 若点击位置为空白行或末尾,
caretRangeFromPoint可能返回null,需 fallback:先range.selectNodeContents(el),再range.collapse(false),且确保el内至少有一个(零宽空格)
fixed 底部输入框 + 软键盘定位失效的根本原因
position: fixed; bottom: 0 看似简单,但在 iOS 上,键盘弹出会压缩 visualViewport,但 fixed 元素并不自动上移——它仍锚定在“屏幕底部”,而非“可视区域底部”。
关键配置缺一不可:
-
viewport必须设height=device-height,且禁用user-scalable=no - 别依赖
adjust-position="false"(仅微信小程序支持) - 滚动目标必须是父容器,不是
contenteditable自身;父容器需有明确布局上下文(如display: block、overflow: visible) - iOS 需在
focus后加requestAnimationFrame再计算位置,否则getBoundingClientRect返回旧值
Android 和 iOS 的 selection.rangeCount 安全读取方式
在 Firefox 或空内容状态下调用 getSelection().getRangeAt(0) 很可能抛出 IndexSizeError: Index out of range,因为此时 selection 存在但 range 为空。
安全写法必须前置检查:
- 始终判断
getSelection().rangeCount > 0再取range - 空内容时主动创建:
const range = document.createRange(); range.selectNodeContents(editor); range.collapse(false); - 不要依赖
focus()后立即读取selection,需加setTimeout(() => { }, 0)或监听selectionchange事件 - 移动端尤其要注意:iOS Safari 在快速切换焦点时可能短暂丢失
rangeCount,建议加try/catch并 fallback 到末尾定位逻辑
光标定位不准的本质,是浏览器把“焦点”和“光标位置”当成两个独立状态处理,而移动端渲染时序、视口计算、输入法合成阶段都比桌面端更脆弱。最容易被忽略的,是没等 visualViewport 更新就去算坐标,或者在 contenteditable 属性刚设为 true 就立刻读 getSelection()——这时 DOM 还没重排,一切坐标都是假的。


















