fix: 修复移动端输入框被键盘遮挡的问题

This commit is contained in:
haorwen
2026-05-16 21:13:21 +08:00
parent 8e8212c580
commit 351da6fa4d
6 changed files with 68 additions and 4 deletions
+1
View File
@@ -5,6 +5,7 @@
/.pnp
.pnp.js
.pnpm-store
.omc
# testing
/coverage
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "vocechat-web",
"version": "0.9.78",
"version": "0.9.79",
"homepage": "https://voce.chat",
"dependencies": {
"@metamask/onboarding": "^1.0.1",
+49
View File
@@ -0,0 +1,49 @@
import { useEffect } from "react";
const isIOS = () =>
/iPad|iPhone|iPod/.test(navigator.userAgent) ||
(navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
/**
* Listens to the VisualViewport API and writes the visible height as a CSS
* custom property (`--vh-visible`) on `document.documentElement`.
*
* On Android mobile browsers the virtual keyboard shrinks the visual viewport
* while `100vh` still refers to the full layout viewport, which causes
* bottom-pinned elements (like the chat input) to be hidden behind the
* keyboard or the browser chrome.
*
* iOS Safari handles this natively via scroll-into-view behaviour and
* `-webkit-fill-available`, so we skip it there to avoid the container
* being over-shrunk when the keyboard opens.
*/
export default function useVirtualKeyboard() {
useEffect(() => {
if (isIOS()) return;
const vv = window.visualViewport;
function update() {
const height = vv ? vv.height : window.innerHeight;
document.documentElement.style.setProperty("--vh-visible", `${height}px`);
}
update();
if (vv) {
vv.addEventListener("resize", update);
vv.addEventListener("scroll", update);
} else {
window.addEventListener("resize", update);
}
return () => {
if (vv) {
vv.removeEventListener("resize", update);
vv.removeEventListener("scroll", update);
} else {
window.removeEventListener("resize", update);
}
};
}, []);
}
+7 -2
View File
@@ -8,6 +8,8 @@ import ChannelModal from "@/components/ChannelModal";
import ErrorCatcher from "@/components/ErrorCatcher";
import Server from "@/components/Server";
import UsersModal from "@/components/UsersModal";
import useVirtualKeyboard from "@/hooks/useVirtualKeyboard";
import { isIOS } from "@/utils";
import ChannelChat from "./ChannelChat";
import DMChat from "./DMChat";
import GuestBlankPlaceholder from "./GuestBlankPlaceholder";
@@ -19,6 +21,7 @@ import VoiceFullscreen from "./VoiceFullscreen";
import { shallowEqual } from "react-redux";
function ChatPage() {
useVirtualKeyboard();
const isHomePath = useMatch(`/`);
const isChatHomePath = useMatch(`/chat`);
const [sessionListVisible, setSessionListVisible] = useState(false);
@@ -65,6 +68,8 @@ function ChatPage() {
const contextId = (+channel_id || callingTo) ?? 0;
console.log("fffff", channel_id, user_id, aside, channelChatVisible);
const mobileHeight = isIOS() ? "h-screen" : "h-[var(--vh-visible,100vh)]";
return (
<ErrorCatcher>
{channelModalVisible && (
@@ -73,7 +78,7 @@ function ChatPage() {
{usersModalVisible && <UsersModal closeModal={toggleUsersModalVisible} />}
<div
className={clsx(
`flex h-screen md:h-full md:pt-2 md:pb-2.5 md:pr-1`,
`flex ${mobileHeight} md:h-full md:pt-2 md:pb-2.5 md:pr-1`,
isGuest ? "guest-container md:px-1" : "md:pr-12"
)}
>
@@ -85,7 +90,7 @@ function ChatPage() {
)}
<div
className={clsx(
"left-container flex-col md:rounded-l-2xl w-full h-screen md:h-full md:max-w-[250px] md:min-w-[268px] shadow-[rgb(0_0_0_/_10%)_-1px_0px_0px_inset] bg-white dark:!bg-gray-800",
`left-container flex-col md:rounded-l-2xl w-full ${mobileHeight} md:h-full md:max-w-[250px] md:min-w-[268px] shadow-[rgb(0_0_0_/_10%)_-1px_0px_0px_inset] bg-white dark:!bg-gray-800`,
isMainPath ? "flex" : "hidden md:flex"
)}
>
+6 -1
View File
@@ -13,6 +13,8 @@ import Tooltip from "@/components/Tooltip";
import UnreadTabTip from "@/components/UnreadTabTip";
import Voice from "@/components/Voice";
import usePreload from "@/hooks/usePreload";
import useVirtualKeyboard from "@/hooks/useVirtualKeyboard";
import { isIOS } from "@/utils";
import FavIcon from "@/assets/icons/bookmark.svg";
import ChatIcon from "@/assets/icons/chat.svg";
import FolderIcon from "@/assets/icons/folder.svg";
@@ -23,6 +25,7 @@ import User from "./User";
import StreamStatus from "@/components/StreamStatus";
function HomePage() {
useVirtualKeyboard();
const { t } = useTranslation();
const dispatch = useDispatch();
const isHomePath = useMatch(`/`);
@@ -66,7 +69,9 @@ function HomePage() {
<Manifest />
{!guest && <Notification />}
<div
className={`vocechat-container flex w-screen h-screen bg-neutral-100 dark:bg-neutral-900`}
className={`vocechat-container flex w-screen ${
isIOS() ? "h-screen" : "h-[var(--vh-visible,100vh)]"
} bg-neutral-100 dark:bg-neutral-900`}
>
{!guest && (
<div
+4
View File
@@ -61,6 +61,10 @@ export function getMessageFromPlateValues(values: ParagraphInput[]): MessageWith
export const isMobile = () =>
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
export const isIOS = () =>
/iPad|iPhone|iPod/.test(navigator.userAgent) ||
(navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
export const getLocalAuthData = () => {
return {
token: localStorage.getItem(KEY_TOKEN) || "",