移动端适配
面试一句话总结:移动端适配经历了几个时代:早期用媒体查询(响应式),后来淘宝的
flexible.js开启了rem动态计算的时代;如今随着浏览器兼容性的提升,基于视口单位 (vw/vh) 的方案已经成为绝对的现代标准。 此外,1px 边框和刘海屏适配是必考的两个坑点。
核心适配方案演进
1. 响应式布局 (Media Query)
通过 @media (max-width: 768px) { ... } 为不同屏幕尺寸编写不同的 CSS 样式。
- 优点:一套代码可以兼容 PC、Pad 和手机端(如 Bootstrap 的栅格系统)。
- 缺点:开发工作量极大,且在屏幕尺寸极其繁杂的移动端中,很难做到真正的“等比例缩放”。
2. rem 适配方案 (以 flexible.js 为代表)
rem 是相对于根元素 <html> 的 font-size 的单位。如果 html 的 font-size 是 16px,那么 1rem 就是 16px。
- 原理:
flexible.js的核心逻辑是监听屏幕宽度的变化,通过 JS 动态计算并设置<html>的font-size为屏幕宽度的1/10。这样我们在写 CSS 时,使用 rem 单位就能实现页面的等比例缩放。 - 缺点:需要引入一段额外的 JS 脚本来计算,并且在页面加载瞬间可能会有一丝“闪烁”或重新计算的过程。
3. 视口单位适配 (vw / vh) - 现代标准
vw (Viewport Width) 代表视口宽度的 1%,vh 代表视口高度的 1%。
如果屏幕宽 375px,那么 1vw = 3.75px。
- 优点:纯 CSS 方案,完全不需要任何 JS 的介入! 原生支持等比缩放,性能极佳。
- 缺点/兼容性坑点:
- 极少数低版本系统(如 Android 4.4 以前或 iOS 8 以前)存在兼容性问题,但目前主流移动端浏览器已全面支持(Can I Use 绿油油一片)。
- 100vh 的滚动条陷阱:在移动端浏览器(尤其是 Safari)中,由于底部的工具栏和顶部的地址栏会自动收缩/展开,导致视口的实际高度发生变化。此时如果设置
height: 100vh,底部内容往往会被浏览器的工具栏遮挡,产生额外的滚动条。 - 解决方案:现代 CSS 提出了新的单位
dvh(Dynamic Viewport Height),它可以根据浏览器工具栏的伸缩动态计算真实可见高度。如果项目不支持dvh,通常需要用 JS 动态获取window.innerHeight并赋值给 CSS 变量。
- 实战做法:在工程化中,我们不需要自己去手算
vw,而是依然在代码里写设计稿标注的px,然后通过 PostCSS 插件(如postcss-px-to-viewport)在打包时自动将px编译成vw。
移动端面试两大神坑
坑点 1:经典的 "1px 边框" 问题
问题现象:在高清屏(Retina 屏,如 iPhone)上,设备的物理像素和独立像素的比例(window.devicePixelRatio,简称 dpr)通常是 2 甚至 3。这就导致你写的 border: 1px solid black,在手机屏幕上实际上是由 2 个或 3 个物理像素渲染的,看起来就比设计稿粗了一倍!
主流解决方案:伪元素 + transform 缩放 (最推荐)
通过给元素追加一个伪元素,让它的宽高变成 200%,然后用 transform: scale(0.5) 把它缩小一半,这样线条的视觉厚度就变成了真正的 0.5px(对应 1 个物理像素)。
.border-1px {
position: relative;
}
.border-1px::after {
content: "";
position: absolute;
top: 0;
left: 0;
width: 200%;
height: 200%;
border: 1px solid #ccc;
transform: scale(0.5); /* 缩小一半 */
transform-origin: 0 0; /* 保持左上角对齐 */
box-sizing: border-box;
pointer-events: none; /* 防止遮挡点击事件 */
}
坑点 2:全面屏/刘海屏的 "安全区域 (Safe Area)" 适配
问题现象:随着 iPhone X 及后续机型的普及,屏幕顶部有了刘海,底部有了小白条(Home Indicator)。如果你的底部导航栏贴在屏幕最底下,就会被小白条遮挡,导致无法点击。
解决方案:利用 CSS 的 env() 函数
- 必须先在 HTML 的
<meta name="viewport">中添加viewport-fit=cover属性,告诉浏览器页面要充满全屏。 - 在 CSS 中使用内置的
env(safe-area-inset-bottom)来获取底部安全距离,并给元素增加对应的padding-bottom。
.bottom-nav {
/* 基础 padding */
padding-bottom: 20px;
/* 适配刘海屏:加上底部的安全区域高度 */
padding-bottom: calc(20px + env(safe-area-inset-bottom));
}