viewport标签必须写为且置于最顶部,否则横屏布局失效;需避免禁用缩放、固定宽度、服务端缺失、单用orientation媒体查询等常见错误,并采用matchmedia监听+尺寸判断+dvh单位+触控热区优化确保横屏适配可靠。

viewport 标签写错,横屏布局就永远不生效——它不是可选配置,而是浏览器是否按设备真实方向渲染的开关。
viewport 必须同时满足两个条件才能触发横屏识别
只写 width=device-width 或只写 initial-scale=1.0 都不行。iOS Safari 会默认以 980px 渲染再缩放,导致 @media (orientation: landscape) 根本不匹配;Android WebView 则可能忽略方向变化,始终走竖屏样式。
必须用这一行,且放在 最顶部:
<meta name="viewport" content="width=device-width, initial-scale=1.0">
- 删掉
user-scalable=no和maximum-scale=1.0:它们会禁用辅助缩放,违反可访问性要求,且在 iOS 13+ 已基本失效 - 别写
width=375这类固定值:折叠屏横屏时 device-width 可能是 844px,硬写死直接崩 - 确保服务端吐出的 HTML 就含该标签:Vue/React SSR 模板里不能只靠客户端 JS 补
@media (orientation: landscape) 单独用大概率失效
单纯监听 orientation 在 iOS Safari 和新版 Chrome 中经常漏触发——它只看宽高比,不看地址栏是否展开、折叠屏是否半开、甚至双击放大后状态是否更新。
更稳的写法是叠加尺寸判断:
@media (orientation: landscape) and (min-aspect-ratio: 1280/720) { /* 横屏逻辑 */ }
或直接响应高度压缩(横屏时 window.innerHeight 常骤降):
@media (max-height: 480px) and (min-width: 640px) { /* 实际横屏场景更准 */ }
- 避免用
100vh:iPhone 14 横屏下100vh实际只有约 390px,推荐改用dvh(Safari 16.4+ / Chrome 105+ 支持),降级方案是min-height: 100%+html, body { height: 100% } -
flex-direction: column不会自动变row:横屏下仍竖排,需在媒体查询里显式改写
JavaScript 监听要用 matchMedia,别碰 orientationchange
orientationchange 事件已在 iOS 15+ 和 Chrome 79+ 中行为不稳定:快速旋转可能跳过,初始加载不触发,甚至返回错误角度值(如 window.orientation === 0 却是横屏)。
正确做法是用 window.matchMedia:
const media = window.matchMedia('(orientation: landscape)');<br>media.addEventListener('change', e => {<br> if (e.matches) { /* 横屏逻辑,比如加 class 或重算根字号 */ }<br>});<br>// 首次加载必须手动检查<br>if (media.matches) { /* 初始化横屏状态 */ }
- 需要动态调整
font-size、切换 DOM 结构、重排容器时,都应放在这里执行 - 不要在
resize事件里做横屏判断:它太频繁,且无法区分是旋转还是键盘弹出 - 若用
clamp()控制字体,注意 iOS Safari 10 以下不支持vw单位设font-size,得加 JS fallback
按钮和表单在横屏下容易“消失”或点不中
横屏后常见问题不是样式没生效,而是触控热区塌陷或定位飞出可视区:
- 按钮位置错乱?别用
position: absolute配死值top: 200px—— 横屏后window.innerHeight突变,但值没重算。改用flex容器居中,或监听matchMedia后重设style.top - 点击区域失效?必须设
min-width: 44px和min-height: 44px(Apple 推荐最小触控尺寸),并加touch-action: manipulation - 表格横向滚动不出现?
<table> 自身不响应 <code>overflow-x,必须用<div> 包裹,设 <code>overflow-x: auto、-webkit-overflow-scrolling: touch,再把<table> 设为 <code>display: block和min-width: max-content复杂点不在代码量,而在横屏是 viewport 解析、CSS 渲染流、JS 事件时机三者博弈的结果——任一环节掉链子,整个适配就断在用户手指落下的那一瞬间。











