1. 原始阶段
- 固定宽度布局(最早期)
- 百分比布局 + em 单位
让我们聊聊移动端适配的”原始阶段”。
还记得智能手机刚开始普及的时候吗?那时候的网页都是为电脑设计的,放在手机上查看时要么太大要么太小,经常需要手动缩放才能看清内容。这就是为什么我们需要专门的移动端适配方案。
最开始的时候,开发者们的做法特别简单粗暴 —— 直接给页面设置一个固定的宽度,比如 320px 或者 640px。代码大概是这个样子:
.page {
width: 320px;
margin: 0 auto; /* 让页面居中显示 */
}
.header {
height: 44px;
background: #f5f5f5;
}
.content {
font-size: 14px;
line-height: 1.5;
}
你可能会问:”这样做不行吗?”确实,这种方案简单直接,写起来也很轻松。但是实际用起来问题可就大了:
想象一下,你拿着一个屏幕宽度只有 300px 的小手机访问这个页面,会发现右边被截了一段,还得左右滑动才能看完整个页面,体验相当糟糕。换成一个 400px 宽的大屏手机,页面两边又会留下大片空白,看起来就像是在浪费屏幕空间。
为了解决这个问题,开发者们开始尝试使用百分比来布局。这样页面就能根据屏幕宽度自动伸缩了:
.page {
width: 100%; /* 宽度占满整个屏幕 */
max-width: 640px; /* 但不能太宽,避免在平板上变形 */
}
.content {
padding: 0 5%; /* 两边留5%的边距 */
}
.left-section {
width: 30%; /* 左边占30% */
float: left;
}
.right-section {
width: 70%; /* 右边占70% */
float: left;
}
这样看起来是不是好多了?页面可以自适应不同的屏幕宽度。但是用百分比也有让人头疼的地方:
-
假设你想让一个元素的高度永远是宽度的一半,你可能会写
height: 50%
。但实际上这样并不会生效,因为父元素如果没有固定高度,子元素的百分比高度就会失效。 -
内边距(padding)和外边距(margin)的百分比值都是相对于父元素的宽度来计算的,这一点特别容易让人困惑。比如:
.box {
width: 300px;
padding: 10%; /* 这里的padding值是300px的10%,也就是30px */
}
- 更要命的是,一些属性用百分比特别别扭,比如想让字体大小随着屏幕变化,用百分比就不太合适。
于是有人想到了用em
这个单位。em
是相对于父元素的字体大小来计算的,听起来挺好用的:
.parent {
font-size: 16px;
}
.child {
font-size: 1.2em; /* 会是 16px * 1.2 = 19.2px */
padding: 1em; /* 会是 19.2px */
margin-bottom: 0.5em; /* 会是 9.6px */
}
但是等你实际用起来就会发现,这简直是个噩梦!因为em
会层层叠加:
.level-1 {
font-size: 1.2em; /* 16px * 1.2 = 19.2px */
}
.level-2 {
font-size: 1.2em; /* 19.2px * 1.2 = 23.04px */
}
.level-3 {
font-size: 1.2em; /* 23.04px * 1.2 = 27.648px */
}
看到这个计算过程,你就知道为什么后来的开发者都不愿意用em
了 —— 这谁能算得清啊!而且一旦某个地方的字体大小改变了,所有用em
的地方都要重新计算,维护起来特别痛苦。
正是因为这些问题,我们才需要更好的解决方案。接下来,就到了响应式布局的时代,媒体查询和 rem 的组合开始大放异彩。
2. 响应式过渡阶段
- 媒体查询 + px
- 媒体查询 + rem
前面我们聊到,用百分比和 em 单位来做移动端适配都不够理想。这时候,CSS3 带来了一个强大的新功能 —— 媒体查询(Media Queries)。有了它,我们终于可以针对不同的设备写不同的样式了!
来看个简单的例子。假设我们在做一个新闻网站,需要在手机和平板上都能好好展示。用媒体查询,我们可以这样写:
/* 默认样式 - 适用于手机 */
.news-container {
width: 100%;
padding: 0 15px;
}
.news-item {
font-size: 14px;
margin-bottom: 10px;
}
/* 平板和小屏电脑 */
@media screen and (min-width: 768px) {
.news-container {
width: 750px;
margin: 0 auto;
padding: 0 20px;
}
.news-item {
font-size: 16px;
margin-bottom: 15px;
}
}
/* 大屏电脑 */
@media screen and (min-width: 1200px) {
.news-container {
width: 1170px;
}
}
这段代码是不是很有意思?它的意思是:
- 在手机上(屏幕小于 768px),新闻列表会占满整个屏幕宽度,字体稍小一些
- 到了平板(屏幕大于等于 768px),内容区域会变成固定的 750px 宽度,字体也会变大一点
- 在大屏电脑上(屏幕大于等于 1200px),内容区域会进一步加宽到 1170px
这样,同一个网站在不同设备上都能获得最佳的阅读体验。但是很快,开发者们又发现了新问题:不同的手机屏幕有不同的设备像素比(Device Pixel Ratio,简称 DPR)。
比如 iPhone 6/7/8 的 DPR 是 2,这意味着我们写的1px
在屏幕上实际显示是2个物理像素
。到了 iPhone X 这样的设备,DPR 更是达到了 3。这就导致同样的14px
字体,在不同手机上看起来大小不一。
而且,你看上面那段代码,我们需要给每种屏幕尺寸都写一套样式,如果设备种类再多一点,代码量就会暴增。维护起来也特别麻烦,改个字体大小可能要改好几处。
这时候,rem 这个 CSS 单位就派上用场了。rem 跟 em 类似,但是它永远都是相对于根元素(html 标签)的字体大小来计算的。比如:
html {
font-size: 16px;
}
.box {
width: 10rem; /* 160px */
height: 5rem; /* 80px */
font-size: 1rem; /* 16px */
}
聪明的开发者们想到了:如果我们能根据屏幕宽度动态设置 html 的 font-size,那不就可以实现所有使用 rem 单位的元素都跟着屏幕大小自动缩放了吗?
/* 在不同屏幕下设置不同的根字体大小 */
@media screen and (max-width: 320px) {
html {
font-size: 14px;
}
}
@media screen and (min-width: 321px) and (max-width: 375px) {
html {
font-size: 16px;
}
}
@media screen and (min-width: 376px) {
html {
font-size: 18px;
}
}
/* 所有尺寸都用rem */
.button {
width: 5rem;
height: 2rem;
font-size: 0.875rem;
border-radius: 0.25rem;
}
这样一来,同样的按钮在不同屏幕上就会自动调整大小,既保持了整体的协调性,又能适应不同的屏幕。而且最重要的是,我们只需要写一次样式,剩下的就交给 rem 自动处理了!
不过,手动设置这些媒体查询断点还是有点麻烦。而且,我们永远不能穷举所有的媒体查询宽度。
那么,能不能让根字体大小完全根据屏幕宽度自动计算?…这个想法直接推动了下一个阶段的到来:淘宝团队的 flexible 方案!
3. 工程化解决方案
- flexible 方案(淘宝)
- lib-flexible(升级版)
- postcss-pxtorem + rem
前面我们说到,手动设置媒体查询的方式还是不够优雅。毕竟,谁也不想写一堆的媒体查询来适配不同的屏幕尺寸。而且,设计师给我们的设计稿通常是固定宽度的(比如 750px),我们还得手动计算每个尺寸对应的 rem 值,这也太麻烦了。
这时候,淘宝的前端团队想出了一个聪明的主意:为什么不让 JavaScript 来帮我们自动计算根字体大小呢?这就是著名的 flexible 方案:
// 核心思路:将屏幕宽度平均分成10份,1rem就等于其中的1份
function setRem() {
// 假设设计稿是750px
const htmlWidth = document.documentElement.clientWidth || document.body.clientWidth
const htmlDom = document.getElementsByTagName('html')[0]
htmlDom.style.fontSize = htmlWidth / 10 + 'px'
}
// 初始化
setRem()
// 窗口变化时重新设置
window.addEventListener('resize', setRem)
这段代码的巧妙之处在于:它把屏幕宽度平均分成 10 份,1rem 就等于其中的 1 份。这样,在 750px 的设计稿上,1rem 就等于 75px。比如设计稿上一个按钮是 150px 宽,我们只要写 2rem 就可以了,简单直观!
不仅如此,flexible 方案还考虑到了设备像素比(DPR)的问题,它会自动设置 viewport 的缩放比例:
const scale = 1 / devicePixelRatio
document
.querySelector('meta[name="viewport"]')
.setAttribute(
'content',
'width=device-width,initial-scale=' +
scale +
',maximum-scale=' +
scale +
',minimum-scale=' +
scale +
',user-scalable=no',
)
这样就能确保在不同 DPR 的设备上显示效果一致。后来,淘宝团队还推出了升级版的 lib-flexible,增加了更多功能和优化。
但是,我们还是要手动计算 px 到 rem 的转换,虽然计算方式简单了(用设计稿的 px 值除以 75),但还是挺烦的。于是postcss-pxtorem
这个神器就登场了!
它是一个 PostCSS 插件,可以自动将 px 转换为 rem。我们只需要简单配置一下:
// postcss.config.js
module.exports = {
plugins: {
'postcss-pxtorem': {
rootValue: 75, // 设计稿宽度的1/10
propList: ['*'], // 需要转换的属性,这里表示全部都转换
selectorBlackList: ['.no-rem'], // 不转换的选择器
minPixelValue: 2, // 小于2px的不转换
},
},
}
有了这个插件,我们就可以直接按照设计稿的 px 值写样式了:
.button {
width: 150px; /* 会被自动转换成 2rem */
height: 60px; /* 会被自动转换成 0.8rem */
font-size: 28px; /* 会被自动转换成 0.373333rem */
border-radius: 8px; /* 会被自动转换成 0.106667rem */
}
这样一来,我们的开发流程就变得超级顺畅:
- 拿到设计稿,直接按照上面的 px 值写样式
- 保存文件时,postcss-pxtorem 自动将 px 转换为 rem
- flexible.js 自动设置根字体大小
- 完美适配各种屏幕!
所以,flexable 解决的是媒体查询无法穷举的问题,通过 JS 动态计算使得适配更加精确和自动化;而 postcss-pxtorem 则解决了”开发时需要手动计算 rem 值”的问题,让开发者可以直接使用设计稿的 px 值,提升了开发效率。这两个工具的组合使用,正好解决了 rem 适配方案中的两大痛点。
不过,随着 viewport 单位(vw、vh)的兼容性越来越好,我们又有了更好的选择。
4. 现代化方案
- vw + postcss-px-to-viewport
- vw + rem 混合方案
前面我们聊到了 flexible 和 postcss-pxtorem 的组合,这个方案用了很长时间。
但是你有没有发现,它还是有点麻烦?首先需要引入 JavaScript 脚本,其次还要配置 postcss 插件。而且 rem 的计算规则(屏幕宽度/10)说实话也不够直观。
这时候,viewport 单位(vw/vh)站了出来说:”让我来试试!”
viewport 单位超级简单:
- 1vw = 视口宽度的 1%
- 1vh = 视口高度的 1%
- 100vw 就是整个视口的宽度
- 100vh 就是整个视口的高度
比如在 375px 宽的手机上,1vw 就等于 3.75px,这比 rem 的计算方式直观多了!
而且,我们有了新的 PostCSS 插件:postcss-px-to-viewport。它的配置比 postcss-pxtorem 还要简单:
// postcss.config.js
module.exports = {
plugins: {
'postcss-px-to-viewport': {
viewportWidth: 750, // 设计稿宽度
viewportHeight: 1334, // 设计稿高度
unitPrecision: 3, // 保留的小数位数
viewportUnit: 'vw', // 转换成的单位
selectorBlackList: ['.ignore'], // 不需要转换的类名
minPixelValue: 1, // 小于1px不转换
mediaQuery: false, // 是否允许在媒体查询中转换px
},
},
}
有了这个插件,我们依然可以按照设计稿的 px 来写样式:
.button {
width: 150px; /* 会被转换成 20vw */
height: 60px; /* 会被转换成 8vw */
font-size: 28px; /* 会被转换成 3.733vw */
border-radius: 8px; /* 会被转换成 1.067vw */
}
看到没有?不需要 JavaScript,不需要计算,样式表里的 px 会自动转换成 vw。而且计算规则特别简单:
vw = px / (设计稿宽度 / 100)
不过,单纯使用 vw 也有一个小问题:当屏幕很大时(比如平板或者 PC),页面元素会变得特别大。
最简单的方案 - 限制容器最大宽度:
.container {
width: 100vw;
max-width: 750px; /* 或其他合适的最大宽度 */
margin: 0 auto;
}
当然,我们也可以结合 rem 来解决这个问题,这就是 vw + rem 混合方案:
/* 设置根字体大小,但限制最大值 */
html {
/* 基准字号使用vw */
font-size: 10vw; /* 在750px设计稿下,1rem = 75px */
/* 限制最大字号 */
@media screen and (min-width: 750px) {
font-size: 75px; /* 750px * 10% = 75px */
}
}
/* 页面内容区域限制最大宽度 */
.container {
width: 7.5rem; /* 等于设计稿宽度 */
margin: 0 auto;
}
/* 其他元素使用rem */
.button {
width: 2rem; /* 原本150px */
height: 0.8rem; /* 原本60px */
font-size: 0.373333rem; /* 原本28px */
}
总结与选型建议
让我们回顾一下移动端适配方案的发展历程:
- 原始阶段:固定宽度和百分比布局,实现简单但适配效果差
- 响应式过渡:媒体查询+rem,开始关注不同设备的适配,但代码量大
- 工程化方案:flexible+postcss-pxtorem,让开发更自动化,但依赖 JS
- 现代化方案:viewport 单位+容器查询,更简单直观,但需要考虑兼容性
各方案对比
方案 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
flexible + rem | - 兼容性好 - 工程化成熟 - 用法简单 |
- 需要引入 JS - 计算不直观 - 配置略繁琐 |
兼容性要求高的项目 |
vw + viewport | - 原生支持 - 计算直观 - 无需 JS |
- 大屏幕缩放问题 - 兼容性要求高 |
现代化项目 |
vw + rem 混合 | - 兼顾灵活性 - 解决大屏问题 - 配置简单 |
- 代码稍复杂 - 需要同时理解两种单位 |
大中型项目 |
选型推荐
-
如果你的项目需要兼容老旧浏览器(如 iOS 8、Android 4.4):
- 推荐使用 flexible + postcss-pxtorem 方案
- 这是经过时间考验的解决方案,兼容性最好
-
如果你在开发一个全新的项目:
- 推荐使用 vw + postcss-px-to-viewport 方案
- 更简单、更直观,且不需要引入额外的 JavaScript
-
如果你的项目需要同时适配移动端和 PC 端:
- 推荐使用 vw + rem 混合方案
- 可以更好地控制在大屏幕下的展示效果
最后的建议:对于新项目,如果不需要考虑兼容性,直接上 vw + postcss-px-to-viewport
是最省心的选择。它既简单又好用,还是未来的趋势。