fix(chat): adapt input bar to soft keyboard in FB in-app browser
FB IAB on Xiaomi (and similar WebViews that don't shrink 100dvh) keeps the chat input covered by the soft keyboard. Fix by writing the visualViewport-derived keyboard height to a CSS variable on <html> and consuming it as padding-bottom on the chat input bar. - Add useKeyboardHeight hook (visualViewport + rAF batching, no re-renders) - Apply --keyboard-height + env(safe-area-inset-bottom) to .bar padding - Add scrollIntoView on textarea focus so the input stays visible - Add viewportFit: cover so safe-area-inset-bottom is reported
This commit is contained in:
@@ -3,4 +3,5 @@
|
||||
*/
|
||||
|
||||
export * from "./use-breakpoint";
|
||||
export * from "./use-keyboard-height";
|
||||
export * from "./use-pull-to-refresh";
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
"use client";
|
||||
/**
|
||||
* 软键盘高度 hook(修复 Facebook IAB / 小米 WebView 不收缩 100dvh 导致输入框被遮挡)
|
||||
*
|
||||
* 通过 window.visualViewport 的 resize/scroll 事件计算键盘高度:
|
||||
* keyboardHeight = max(0, innerHeight - (visualViewport.height + offsetTop))
|
||||
* 写到 <html> 的 CSS 变量 --keyboard-height(默认 0px)。
|
||||
* 不返回 React state,避免键盘动画期间触发 re-render。
|
||||
* rAF 合并写入;卸载时清理监听并把变量重置为 0px,防止路由切换后状态泄漏。
|
||||
*
|
||||
* 行为:
|
||||
* - iOS Safari / 现代 Chrome Android:visualViewport.height 随键盘收缩,差值 ≈ 0,
|
||||
* padding 不变,无视觉副作用。
|
||||
* - FB IAB / 小米等不收缩 innerHeight 的 WebView:差值即为键盘高度,CSS 即可适配。
|
||||
* - visualViewport 不可用:降级为 0(保留原行为,不引入新问题)。
|
||||
*/
|
||||
import { useEffect } from "react";
|
||||
|
||||
const VAR_NAME = "--keyboard-height";
|
||||
|
||||
function compute(): number {
|
||||
if (typeof window === "undefined") return 0;
|
||||
const vv = window.visualViewport;
|
||||
if (vv) {
|
||||
return Math.max(0, window.innerHeight - (vv.height + vv.offsetTop));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function useKeyboardHeight(): void {
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
let rafId: number | null = null;
|
||||
let last = -1;
|
||||
|
||||
const apply = () => {
|
||||
rafId = null;
|
||||
const h = Math.round(compute());
|
||||
if (h === last) return;
|
||||
last = h;
|
||||
document.documentElement.style.setProperty(VAR_NAME, `${h}px`);
|
||||
};
|
||||
const schedule = () => {
|
||||
if (rafId != null) return;
|
||||
rafId = window.requestAnimationFrame(apply);
|
||||
};
|
||||
|
||||
const vv = window.visualViewport;
|
||||
if (vv) {
|
||||
vv.addEventListener("resize", schedule);
|
||||
vv.addEventListener("scroll", schedule);
|
||||
} else {
|
||||
window.addEventListener("resize", schedule);
|
||||
}
|
||||
|
||||
apply();
|
||||
|
||||
return () => {
|
||||
if (rafId != null) window.cancelAnimationFrame(rafId);
|
||||
if (vv) {
|
||||
vv.removeEventListener("resize", schedule);
|
||||
vv.removeEventListener("scroll", schedule);
|
||||
} else {
|
||||
window.removeEventListener("resize", schedule);
|
||||
}
|
||||
document.documentElement.style.setProperty(VAR_NAME, "0px");
|
||||
};
|
||||
}, []);
|
||||
}
|
||||
Reference in New Issue
Block a user