🔍 Viewport 视口与适配基础
Viewport 是移动端开发最基础也最重要的概念。没有正确配置 viewport,页面在手机上会以缩放形式显示,用户体验极差。
三大视口概念
| 视口类型 | 含义 | 获取方式 |
|---|---|---|
| 布局视口 | CSS 布局的基准区域,默认约 980px | document.documentElement.clientWidth |
| 视觉视口 | 用户当前看到的区域,可缩放 | window.visualViewport.width |
| 理想视口 | 设备屏幕的 CSS 像素宽度 | screen.width |
视口联动机制
三个视口并非独立运作,它们之间的联动关系直接影响页面表现:
- 缩放时:用户双指缩放时,布局视口宽度不变,视觉视口变小(可看到的内容减少),这是浏览器缩放的本质
- 横竖屏切换:布局视口宽高会交换(
clientWidth与clientHeight互换),需用媒体查询@media (orientation)处理 initial-scale=1.0的作用:使布局视口宽度等于理想视口宽度,确保 CSS 像素与设备宽度 1:1 对应,是适配的基石
CSS 像素、物理像素与设备像素比
理解这三种像素是掌握移动端适配的前提:
| 概念 | 含义 | 示例(iPhone 14) |
|---|---|---|
| 物理像素 | 屏幕真实的物理发光点,由硬件决定 | 1170 × 2532 |
| CSS 像素(逻辑像素) | CSS 中使用的 px 单位,与屏幕密度无关 | 390 × 844 |
| 设备像素比(DPR) | 物理像素 / CSS 像素,window.devicePixelRatio 可获取 | 3(物理/CSS = 1170/390) |
💡 理解记忆
CSS 中写 width: 375px,在 DPR=2 的设备上实际占用 750 个物理像素点。这就是为什么 1px 在 Retina 屏上看着"太细"——实际上它只占了一半的物理像素密度。
标准 Viewport Meta 标签
<!-- ⭐ 移动端必须添加,放在 <head> 最前面 -->
<meta name="viewport"
content="width=device-width, initial-scale=1.0,
maximum-scale=1.0, minimum-scale=1.0,
user-scalable=no, viewport-fit=cover">
<!-- 参数详解 -->
<!-- width=device-width → 布局视口 = 设备宽度(理想视口)-->
<!-- initial-scale=1.0 → 初始缩放比例 1:1 -->
<!-- maximum-scale=1.0 → 最大缩放比例(禁止放大)-->
<!-- minimum-scale=1.0 → 最小缩放比例 -->
<!-- user-scalable=no → 禁止用户手动缩放 -->
<!-- viewport-fit=cover → 适配 iPhone X+ 刘海屏安全区域 -->
⚠️ 关于 user-scalable=no
- 应用型页面(SPA)可以设置禁止缩放,防止双击误触缩放
- 内容型页面(文章、文档)建议保留缩放能力,保障无障碍访问
- iOS 10+ 中该属性可能被 Safari 忽略
💡 内容型 vs 应用型页面的视口配置推荐
- 内容型页面(文章、文档、博客):保留缩放能力,保障无障碍访问——
content="width=device-width, initial-scale=1.0, maximum-scale=3.0, minimum-scale=0.5, user-scalable=yes" - 应用型页面(SPA、后台管理、工具类):禁止缩放,防止误触——
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"
通过 JS 获取设备与屏幕信息
// ===== 获取设备与屏幕信息 =====
const deviceInfo = {
// 设备像素比(DPR):物理像素 / CSS 像素
dpr: window.devicePixelRatio || 1,
// 屏幕尺寸(CSS 像素)
screenWidth: screen.width,
screenHeight: screen.height,
// 可视区域尺寸(布局视口)
viewportWidth: document.documentElement.clientWidth,
viewportHeight: document.documentElement.clientHeight,
// 物理分辨率
physicalWidth: screen.width * (window.devicePixelRatio || 1),
physicalHeight: screen.height * (window.devicePixelRatio || 1),
// 判断设备类型
isMobile: /Mobi|Android|iPhone/i.test(navigator.userAgent),
isIOS: /iPhone|iPad|iPod/i.test(navigator.userAgent),
isAndroid: /Android/i.test(navigator.userAgent),
isWeChat: /MicroMessenger/i.test(navigator.userAgent),
};
console.table(deviceInfo);
监听 visualViewport 变化(软键盘弹起 / 地址栏收起时触发)
// ===== 监听 visualViewport 变化 =====
// 适用场景:软键盘弹起、地址栏收起/展开、分屏模式切换
// API 支持度:iOS Safari 9+ / Android Chrome 61+
if (window.visualViewport) {
window.visualViewport.addEventListener('resize', function () {
console.log({
visualWidth: window.visualViewport.width,
visualHeight: window.visualViewport.height,
offsetTop: window.visualViewport.offsetTop,
});
});
} else {
// 降级方案:监听 window.resize
window.addEventListener('resize', function () {
console.log('innerHeight 变化:', window.innerHeight);
});
}
常见视口问题 FAQ
| 问题现象 | 原因 | 解决方案 |
|---|---|---|
| 页面在手机上显示得很小,像桌面缩放的版本 | 缺少 viewport meta 标签,浏览器使用默认布局视口(约 980px) | 在 <head> 中添加 <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
iOS Safari 上 100vh 元素超出屏幕底部,出现滚动条 |
Safari 的地址栏/工具栏会影响 vh 的计算,100vh 包含了地址栏高度 |
使用新的 dvh(Dynamic Viewport Height)单位,或通过 JS 动态设置 window.innerHeight |
| 横屏后页面布局错乱,内容显示不全 | 布局视口宽度随横竖屏切换而变化,未做相应适配 | 使用 @media (orientation: landscape) 媒体查询调整布局,给容器设置 min-width: 320px |
| 华为/三星折叠屏设备上页面显示异常 | 折叠屏展开时 device-width 动态变化,视口宽度翻倍 |
使用 window.visualViewport 监听尺寸变化;配合 CSS @media (device-posture: folded) 或宽高比媒体查询 |