fix(viewport): suppress ios facebook keyboard resize

This commit is contained in:
2026-07-03 11:58:46 +08:00
parent 654b51b45e
commit e852fb62e9
+57 -4
View File
@@ -2,6 +2,8 @@
import { useEffect, type ReactNode } from "react"; import { useEffect, type ReactNode } from "react";
import { BrowserDetector, Logger, PlatformDetector } from "@/utils";
const CSS_VAR_VIEWPORT_HEIGHT = "--app-viewport-height"; const CSS_VAR_VIEWPORT_HEIGHT = "--app-viewport-height";
const CSS_VAR_VISIBLE_HEIGHT = "--app-visible-height"; const CSS_VAR_VISIBLE_HEIGHT = "--app-visible-height";
const CSS_VAR_VISIBLE_OFFSET_TOP = "--app-visible-offset-top"; const CSS_VAR_VISIBLE_OFFSET_TOP = "--app-visible-offset-top";
@@ -9,6 +11,9 @@ const CSS_VAR_SAFE_TOP = "--app-safe-top";
const CSS_VAR_SAFE_RIGHT = "--app-safe-right"; const CSS_VAR_SAFE_RIGHT = "--app-safe-right";
const CSS_VAR_SAFE_BOTTOM = "--app-safe-bottom"; const CSS_VAR_SAFE_BOTTOM = "--app-safe-bottom";
const CSS_VAR_SAFE_LEFT = "--app-safe-left"; const CSS_VAR_SAFE_LEFT = "--app-safe-left";
const IOS_KEYBOARD_VIEWPORT_DELTA_PX = 120;
const log = new Logger("ProvidersViewportCssVarsProvider");
export interface ViewportCssVarsProviderProps { export interface ViewportCssVarsProviderProps {
children: ReactNode; children: ReactNode;
@@ -28,6 +33,12 @@ export function ViewportCssVarsProvider({
if (typeof window === "undefined") return; if (typeof window === "undefined") return;
let frameId: number | null = null; let frameId: number | null = null;
let stableLayoutHeight = window.innerHeight;
let stableVisibleHeight = window.visualViewport?.height ?? window.innerHeight;
let stableVisibleOffsetTop = window.visualViewport?.offsetTop ?? 0;
let iosFacebookKeyboardSuppressed = false;
const isIOSFacebookInAppBrowser =
PlatformDetector.isIOS() && BrowserDetector.isFacebookInAppBrowser();
const applyVars = () => { const applyVars = () => {
frameId = null; frameId = null;
@@ -38,12 +49,42 @@ export function ViewportCssVarsProvider({
const visibleHeight = viewport?.height ?? layoutHeight; const visibleHeight = viewport?.height ?? layoutHeight;
const visibleWidth = viewport?.width ?? layoutWidth; const visibleWidth = viewport?.width ?? layoutWidth;
const visibleOffsetTop = viewport?.offsetTop ?? 0; const visibleOffsetTop = viewport?.offsetTop ?? 0;
const shouldSuppressIOSFacebookKeyboardViewport =
isIOSFacebookInAppBrowser &&
isEditableElementFocused() &&
stableLayoutHeight - visibleHeight > IOS_KEYBOARD_VIEWPORT_DELTA_PX;
const nextLayoutHeight = shouldSuppressIOSFacebookKeyboardViewport
? stableLayoutHeight
: layoutHeight;
const nextVisibleHeight = shouldSuppressIOSFacebookKeyboardViewport
? stableVisibleHeight
: visibleHeight;
const nextVisibleOffsetTop = shouldSuppressIOSFacebookKeyboardViewport
? stableVisibleOffsetTop
: visibleOffsetTop;
root.style.setProperty(CSS_VAR_VIEWPORT_HEIGHT, `${layoutHeight}px`); if (!shouldSuppressIOSFacebookKeyboardViewport) {
root.style.setProperty(CSS_VAR_VISIBLE_HEIGHT, `${visibleHeight}px`); stableLayoutHeight = layoutHeight;
stableVisibleHeight = visibleHeight;
stableVisibleOffsetTop = visibleOffsetTop;
iosFacebookKeyboardSuppressed = false;
} else if (!iosFacebookKeyboardSuppressed) {
iosFacebookKeyboardSuppressed = true;
log.debug("[viewport] suppress ios facebook keyboard viewport", {
layoutHeight: Math.round(layoutHeight),
visibleHeight: Math.round(visibleHeight),
visibleOffsetTop: Math.round(visibleOffsetTop),
stableLayoutHeight: Math.round(stableLayoutHeight),
stableVisibleHeight: Math.round(stableVisibleHeight),
stableVisibleOffsetTop: Math.round(stableVisibleOffsetTop),
});
}
root.style.setProperty(CSS_VAR_VIEWPORT_HEIGHT, `${nextLayoutHeight}px`);
root.style.setProperty(CSS_VAR_VISIBLE_HEIGHT, `${nextVisibleHeight}px`);
root.style.setProperty( root.style.setProperty(
CSS_VAR_VISIBLE_OFFSET_TOP, CSS_VAR_VISIBLE_OFFSET_TOP,
`${visibleOffsetTop}px`, `${nextVisibleOffsetTop}px`,
); );
root.style.setProperty(CSS_VAR_SAFE_TOP, "env(safe-area-inset-top, 0px)"); root.style.setProperty(CSS_VAR_SAFE_TOP, "env(safe-area-inset-top, 0px)");
root.style.setProperty( root.style.setProperty(
@@ -59,7 +100,7 @@ export function ViewportCssVarsProvider({
"env(safe-area-inset-left, 0px)", "env(safe-area-inset-left, 0px)",
); );
root.dataset.viewportWidth = String(Math.round(visibleWidth)); root.dataset.viewportWidth = String(Math.round(visibleWidth));
root.dataset.viewportHeight = String(Math.round(visibleHeight)); root.dataset.viewportHeight = String(Math.round(nextVisibleHeight));
}; };
const scheduleApply = () => { const scheduleApply = () => {
@@ -85,3 +126,15 @@ export function ViewportCssVarsProvider({
return children; return children;
} }
function isEditableElementFocused(): boolean {
const activeElement = document.activeElement;
if (!(activeElement instanceof HTMLElement)) return false;
const tagName = activeElement.tagName.toLowerCase();
return (
tagName === "input" ||
tagName === "textarea" ||
activeElement.isContentEditable
);
}