100vh在移动端富文本编辑器中失效,因软键盘弹出时浏览器不更新视口高度且不触发resize;应改用visualviewport api获取真实可视区域高度,微信x5内核需降级处理。

为什么 100vh 在富文本编辑器里完全失效
移动端 HTML 编辑器(比如基于 contenteditable 的自定义编辑器、Quill、TinyMCE 或微信内置 textarea 替代方案)一旦聚焦,软键盘弹出时,100vh 就彻底“失联”:它既不更新,也不触发 resize,更不会自动滚动到光标位置。这不是 bug,是浏览器对编辑区域的特殊处理逻辑——尤其在安卓 WebView 和微信内置浏览器中,contenteditable 元素的焦点行为常绕过标准视口事件流。
常见错误现象包括:
• 编辑器容器高度卡死在初始值,内容被键盘截断
• 光标定位错乱,输入时页面疯狂上跳或下坠
• 顶部工具栏(position: fixed)直接消失或悬浮在键盘上方
• iOS 下收起键盘后,页面停留在偏移位置,无法自动回滚
- 别用
height: 100vh做编辑器外层容器高度 —— 它在键盘弹出前后都是静态快照 - 避免监听
focus/blur后直接设body.style.height—— 这会破坏 iOS 的原生滚动锚定机制 - 不要在
resize回调里频繁读取document.documentElement.clientHeight—— 安卓部分 WebView 会返回抖动值(如 642 → 638 → 645),引发 layout thrashing
visualViewport 是唯一可靠的高度感知源
visualViewport API 返回的是当前用户真正能看到的区域尺寸,不受软键盘是否弹出影响,且 iOS Safari 16.4+、Chrome 105+、Edge 105+ 均已稳定支持。它比 window.innerHeight 更精准,也比轮询 document.body.clientHeight 更轻量。
实操建议:
- 初始化时记录
visualViewport.height作为“无键盘基准高度” - 监听
visualViewport.addEventListener('resize', handler),而非window.resize - 当
visualViewport.height显著小于基准值(例如差值 > 150px),判定为键盘弹出;恢复接近基准值时视为收起 - 注意:微信内置浏览器(X5 内核)不支持
visualViewport,需降级到focusin/focusout+setTimeout检测滚动偏移
示例关键逻辑:
let baseHeight = visualViewport?.height || window.innerHeight;
visualViewport?.addEventListener('resize', () => {
const current = visualViewport.height;
if (current <h3>编辑器容器必须用 <code>100dvh</code> + <code>env(safe-area-inset-bottom)</code> 组合</h3><p>仅靠 JS 监听无法解决布局塌陷问题。CSS 层面必须切断软键盘对高度单位的污染:<code>100dvh</code>(dynamic viewport height)始终代表设备物理屏幕高度,与键盘状态无关;而 <code>env(safe-area-inset-bottom)</code> 可预留底部安全区,防止键盘遮挡工具栏。</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/ai/4203" title="AI Prompt Generator"><img
src="https://img.php.cn/upload/ai_manual/001/246/273/178599613951133.png" alt="AI Prompt Generator" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/ai/4203" title="AI Prompt Generator" class="overflowclass">AI Prompt Generator</a>
<p class="overflowclass">AI Prompt Generator是一款AI提示词工具,什么 A...。</p>
</div>
<a rel="nofollow" href="/ai/4203" title="AI Prompt Generator" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div><p>正确写法:</p><pre class="brush:php;toolbar:false;">.editor-container {
height: 100dvh;
padding-bottom: env(safe-area-inset-bottom);
/* 避免被键盘顶起 */
contain: layout style;
}兼容性兜底(针对不支持 100dvh 的旧环境):
- 对 Safari 15.6–16.3、部分安卓 WebView,用
@supports not (height: 100dvh)降级为 JS 注入的 CSS 变量 - 禁止同时设置
min-height: 100vh—— 会与100dvh冲突,导致安卓下双倍高度挤压 - 若编辑器内嵌在
position: fixed工具栏下方,工具栏自身也需加env(safe-area-inset-bottom)补白,否则会被键盘硬顶
光标滚动必须绕过浏览器默认行为
软键盘弹出时,浏览器对 contenteditable 的自动滚动不可信:iOS 常滚动过度,安卓则经常不滚动。手动调用 scrollIntoView 也容易触发多次重排,造成视觉抖动。
更稳的做法是:
- 在
focusin后用requestAnimationFrame延迟一次执行,避开浏览器初始滚动时机 - 不用
scrollIntoView(true),改用{ block: 'nearest', inline: 'nearest' }减少强制位移 - 计算光标位置时,优先读
getSelection().getRangeAt(0).getBoundingClientRect(),而非依赖父容器 offsetTop - 对微信环境单独加判断:若检测到 X5 内核,禁用所有
scrollIntoView,改用scrollTop手动微调(需先preventDefaulttouchmove)
关键避坑点:所有涉及 getBoundingClientRect() 或 scrollTop 的操作,必须包裹在 requestAnimationFrame 中,否则在快速连续聚焦/切换输入框时必然抖动。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










