iOS 软键盘顶起与安全区域避坑:移动端 Web 视口适配方案

在做移动端 Web App、手账工具或在线编辑器时,最让前端工程师头皮发麻的莫过于 iOS Safari 上的两个“视觉破坏王”:
- 底部安全区域(Safe Area)与黑条遮挡:iPhone 底部的 Home Indicator 黑条会把页面最底部的按钮直接盖住。
- 软键盘弹出时的视口灾难(Keyboard Viewport Squish):当用户点击输入框唤起虚拟键盘时,Safari 会机械地把整个网页向上滚动并发生奇怪的视口压缩,导致原本使用
fixed固定在底部的操作栏悬浮在屏幕正中央,或者把输入框本身顶出屏幕外。
今天我把在真机上经过几十轮测试提炼出的移动端 Web 视口与软键盘适配完整避坑指南公开出来。
+--------------------------------------------------------------------+
| 移动端 Web 视口与软键盘适配避坑图解 |
+--------------------------------------------------------------------+
| [HTML viewport 声明: viewport-fit=cover] |
| | |
| v (解锁全屏安全区域 safe-area-inset) |
| [CSS 容器: min-height: 100dvh + padding-bottom: env(safe-area...)] |
| | |
| v (监听 window.visualViewport 动态视口高度) |
| [软键盘唤起时: 动态修正 fixed 定位底边距,消除虚浮与错位] |
+--------------------------------------------------------------------+
1. 基础门票:viewport-fit=cover 声明
在 index.html 的 <head> 中,必须显式声明 viewport-fit=cover,浏览器才会允许页面内容延伸到刘海屏与底部黑条区域:
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
/>
配合 CSS 环境变量使用:
/* 确保底部悬浮栏避开 iPhone 底部操作条 */
.bottom-bar {
padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px));
}
2. 终极解法:Visual Viewport API 监听真实键盘高度
在 iOS 13+ 现代浏览器中,window.innerHeight 在软键盘弹出时并不会实时变化,导致传统的监听 resize 事件失效。
真正能够精准捕获软键盘弹出并计算真实可用高度的是 window.visualViewport(可视视口 API):
import { useEffect, useState } from "react";
export function useVisualViewport() {
const [viewportHeight, setViewportHeight] = useState<number>(() =>
typeof window !== "undefined" && window.visualViewport
? window.visualViewport.height
: typeof window !== "undefined"
? window.innerHeight
: 800
);
const [keyboardHeight, setKeyboardHeight] = useState<number>(0);
useEffect(() => {
if (!window.visualViewport) return;
const handleResize = () => {
const vh = window.visualViewport!.height;
const windowH = window.innerHeight;
setViewportHeight(vh);
// 计算键盘弹出的实际像素高度
setKeyboardHeight(Math.max(0, windowH - vh));
};
window.visualViewport.addEventListener("resize", handleResize);
window.visualViewport.addEventListener("scroll", handleResize);
return () => {
window.visualViewport?.removeEventListener("resize", handleResize);
window.visualViewport?.removeEventListener("scroll", handleResize);
};
}, []);
return { viewportHeight, keyboardHeight };
}
3. 在 React 编辑器组件中的动态定位
有了 keyboardHeight,我们可以直接将底部操作栏的 bottom 偏移量动态绑定到真实键盘上边缘:
import React from "react";
import { useVisualViewport } from "@/hooks/useVisualViewport";
export const MobileJournalEditor: React.FC = () => {
const { keyboardHeight } = useVisualViewport();
return (
<div className="flex flex-col min-h-screen bg-[#FAF7F0]">
<textarea
placeholder="写下当下的碎碎念..."
className="flex-1 p-6 bg-transparent resize-none outline-none text-base text-[#2D2B2A]"
/>
{/* 底部随键盘平滑同步贴合的操作栏 */}
<div
style={{ transform: `translateY(-${keyboardHeight}px)` }}
className="fixed bottom-0 left-0 right-0 p-4 bg-[#FAF7F0]/95 backdrop-blur-md border-t-2 border-[#2D2B2A] flex justify-between items-center transition-transform duration-100 ease-out"
>
<span className="text-xs text-stone-500">🌿 听汐手账输入中</span>
<button className="px-4 py-1.5 bg-[#88AA99] text-white text-xs font-bold rounded-full border-2 border-[#2D2B2A] shadow-[2px_2px_0px_#2D2B2A]">
完成
</button>
</div>
</div>
);
};
把移动端的各种边缘畸变抹平,让 Web 应用在手机上呈现出丝毫不输原生 App 的精致与笃定。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/weixin_63764436/article/details/164465640




