做移动端页面有一段时间了,总结下工作中常用的几种移动端适配方案。
基础
网上已经有非常多的基础知识总结,不再赘诉,详情可以见
其中容易搞混的概念是视口
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1,viewport-fit=cover">
meta
标签中的viewport
属性,就是视图
的含义
视口分为
- 布局视口
- 视觉视口
- 理想视口
布局视口
也就是<meta name="viewport" content="width=device-width">
中width
属性的含义
我们在css中写的所有样式,就是相对于布局视口
进行布局的
默认情况下,移动端的布局视口并不是屏幕宽度,而是一般在768px ~ 1024px间(大部分情况下980px)
可以通过document.documentElement.clientWidth
获取 (根据width
和initial-scale
来确定)
视觉视口
视觉视口是指用户通过设备屏幕看到的区域,默认等于当前浏览器的窗口大小(当initial-scale
为1)
当用户对浏览器进行缩放时,不会改变布局视口的大小,所以页面布局是不变的,但是缩放会改变觉视口的大小
可以通过window.innerWidth
获取 (会随着缩放进行改变)
放大页面,此时window.innerWidth
反而减小 (页面放大,你看到的东西也变少了)
理想视口
理想视口是指网站在移动设备中的理想大小,这个大小就是设备的屏幕大小
也就是<meta name="viewport" content="width=device-width">
中device-width
的含义
可以通过screen.width
获取 (常量,不会改变)
initial-scale
<meta name="viewport" content="width=device-width, initial-scale=0.5">
根据公式initial-scale = 理想视口宽度 / 视觉视口宽度
假设理想视口宽度为414px
(device-width),此时设置initial-scale
为0.5,那么视觉视口宽度就是414 / 0.5 = 818
如果这时你获取document.documentElement.clientWidth
(布局视口)的值,会发现不是414px
而是818px
结论: 布局视口宽度取的是width和视觉视口宽度的最大值
思考题:
<meta name="viewport" content="width=600, initial-scale=2">
假设理想视口宽度为414px
(device-width),此时document.documentElement.clientWidth
(布局视口)的值是多少?
视觉视口 = 414 / 2 = 207
布局视口 = Math.max(207, 600)
布局视口 = 600
总结
document.documentElement.clientWidth
: 布局视口,css中一般写成width=device-width
window.innerWidth
: 视觉视口,页面缩放都会实时改变该值screen.width
: 理想视口,页面屏幕大小(设备独立像素),也就是css中的device-width
常见适配方案
简单一句话概括:移动端适配就是在进行屏幕宽度
的等比例缩放:
平时我们开发中,拿到的移动端设计稿一般是750 * 1334
尺寸大小( iPhone6 的设备像素为标准的设计图)。那如果在750px
设计稿上量出的元素宽度为100px
,那么在375px
宽度的屏幕下,这个元素宽度就应该等比例缩放成50px
。
所以适配的难点是:如果实现页面的等比例缩放?
Rem 方案
该方案的核心就是:所有需要动态布局的元素,不再使用px
固定尺寸,而是采用rem
相对尺寸
rem
的大小是相对于根元素html
的字体大小:如果html
的font-size
为100px,那么1rem
就等于100px
现在我们假定:
750px
屏幕下 html
的font-size
为100px,也就是1rem
为100px,那么200px
宽度的.box
元素,就应该写成2rem
.box {
/* 750px屏幕下,200px */
width: 2rem;
}
那么现在:
375px
屏幕下,我们需要.box
元素渲染成100px
.box {
width: 2rem;
}
由于.box
的宽度仍然是2rem
,因此,这时候我们就需要1rem
为50px,也就是说,此时html
的font-size
为50px
于是此时,我们可以得出一个公式:
(750) / (100) = (当前屏幕尺寸) / (当前屏幕1rem)
把这个公式进行一次数学转换就能得到:
(当前屏幕1rem) = 100 * (当前屏幕尺寸) / 750
翻译成js语言就是
document.documentElement.style.fontSize = 100 * (document.documentElement.clientWidth) / 750 + 'px';
将代码优化一下
const PAGE_WIDTH = 750; // 设计稿的宽度
const PAGE_FONT_SIZE = 100;// 设计稿1rem的大小
const setView = () => {
//设置html标签的fontSize
document.documentElement.style.fontSize = PAGE_FONT_SIZE * (document.documentElement.clientWidth) / PAGE_WIDTH + 'px';
}
window.onresize = setView; // 如果窗口大小发生改变,就触发 setView 事件
setView()
考虑到Andorid端字体渲染的问题以及页面大小变化的监听,最终的代码如下:
(function () {
var timer = null;
var PAGE_WIDTH = 750; // 设计稿的宽度
var PAGE_FONT_SIZE = 100;// 设计稿1rem的大小
function onResize() {
var e = PAGE_FONT_SIZE * document.documentElement.clientWidth / PAGE_WIDTH;
document.documentElement.style.fontSize = e + 'px';
// 二次计算缩放像素,解决移动端webkit字体缩放bug
var realitySize = parseFloat(window.getComputedStyle(document.documentElement).fontSize);
if (e !== realitySize) {
e = e * e / realitySize;
document.documentElement.style.fontSize = e + 'px';
}
}
window.addEventListener('resize', function () {
if (timer) clearTimeout(timer);
timer = setTimeout(onResize, 100);
});
onResize();
})();
注意的是:我们取 100px
作为设计稿的1rem,是因为方便计算,比如设计稿上量出250px
,我们就可以很容易的计算出为2.5rem
。
我们当然也可以把 50px
作为设计稿的1rem,这时设计稿上的250px
,就要写成5rem
。
其实我们也可以借助于postcss-pxtorem或者SCSS
函数来帮我们自动转换单位
@function px2rem($px) {
// 根元素字体为100px
@return $px / 100 * 1rem;
}
.box {
width: px2rem(200);
}
通过Rem方案,需要动态缩放的元素,我们使用rem
相对单位,不需要缩放的元素,我们仍然可以使用px
固定单位。
不过在大屏设备下(例如ipad或者pc端),由于我们的页面是等比例缩放,这时候页面的元素会被放大很多(屏幕宽度大,导致根元素字体1rem也变大)。但是在大屏下,我们真正希望的是用户看到更多的内容,这时候我们可以使用媒体查询的方式来限制根元素的字体,从而防止在大屏下元素过大的问题。
@media screen and (min-width: 450px) {
html {
font-size: 50px !important;
}
}
或者修改js脚本的逻辑
const PAGE_WIDTH = 750; // 设计稿的宽度
let PAGE_FONT_SIZE = 100;// 设计稿1rem的大小
const setView = () => {
if (document.documentElement.clientWidth > 450) {
// 大屏下减小根元素字体
PAGE_FONT_SIZE = 50;
}
document.documentElement.style.fontSize = PAGE_FONT_SIZE * (document.documentElement.clientWidth) / PAGE_WIDTH + 'px';
}
VW 方案
vw 是相对单位,1vw 表示屏幕宽度的 1%
其实我们的REM方案
就是VW方案
的模拟,之前我们有一个公式:
(750) / (100) = (当前屏幕尺寸) / (当前屏幕1rem)
换一个转换方式:
(当前屏幕1rem) = (当前屏幕尺寸) / 7.5
而 vw 单位其实就是:
(当前屏幕1vw) = (当前屏幕尺寸) / 100
因此,REM方案
就是用 JS 把屏幕宽度分成了7.5份,而 CSS3 中新增的vw
单位,原生实现了把屏幕宽度分成了100份
所以,在VW方案
中,我们不再需要使用JS脚本了!
750px
设计稿中,1vw
等于7.5px
(750 / 100),因此,在设计稿中,量出200px
的宽度,就因为写成26.667vw
(200 / 7.5)
.box {
/* 750px屏幕下,200px */
width: 26.667vw;
}
不过使用vw
换算,并不像rem
那么方便,这时候我们可以借助postcss-px-to-viewport或者SCSS
函数来帮我们自动转换单位
@function px2vw($px) {
@return $px / 750 * 100vw;
}
.box {
width: px2vw(200);
}
同样,在大屏设备下,由于屏幕宽度大,所以页面的元素同样会放大很多(屏幕宽度大,1vw也很大)。但是由于vw
是相对屏幕宽度的,所以我们不能像REM方案
中一样,手动控制html
的根字体大小,这也是使用VW方案
的一个缺点。
REM + VW 方案
REM方案
的优势是可以手动控制rem
的大小,防止屏幕太大时,页面元素也缩放很大,但是缺点就是需要使用JS
。VW方案
刚好相反,无需使用JS
但是无法手动控制vw
的大小。
其实我们可以把两者结合:
html {
/* 750px 的设计图,1rem = 100px */
font-size: calc(100 * 100vw / 750);
}
.box {
/* 750px屏幕下,200px */
width: 2rem;
}
对于布局元素,我们仍然使用rem
单位。但是对于根元素的字体大小,我们不需要使用JS来动态计算了
100 * (document.documentElement.clientWidth) / 750
这段js可以直接使用css来实现
calc(100 * 100vw / 750)
对于大屏设备,我们使用媒体查询
@media screen and (min-width: 450px) {
html {
font-size: calc(50 * 100vw / 750);
}
}
更详细的vw+rem布局方案
可以见《基于vw等viewport视区单位配合rem响应式排版和布局》
viewport 缩放方案
还有一种更简单粗暴的方法,就是我们设置initial-scale
我们的布局完全基于设计稿750px
,布局元素单位也使用px
固定单位 (布局视口写死750px)
对于375px
宽度,我们就将整个页面缩放0.5
:
<meta name="viewport" content="width=750, initial-scale=0.5, minimum-scale=0.5, maximum-scale=0.5, user-scalable=0">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>demo</title>
<script>
var clientWidth = document.documentElement.clientWidth;
var viewport = document.querySelector('meta[name="viewport"]');
var viewportWidth = 750;
var viewportScale = clientWidth / viewportWidth;
viewport.setAttribute('content', 'width=' + viewportWidth + ', initial-scale=' + viewportScale + ', minimum-scale=' + viewportScale + ', maximum-scale=' + viewportScale + ', user-scalable=0');
</script>
</head>
.box {
width: 200px;
}
此方案的缺点: 整个页面都被缩放了,对于不想缩放的元素无法控制。
市面上一些营销H5页面,由于是通过后台可视化拖拽搭建出来的,为了适配各种尺寸的屏幕,该方案是成本最低的实现(易企秀就是使用这种方案)
实战
我们拿线上B站的会员购作为示例
请使用chrome开发者工具模拟移动端设备查看
源码直接右键查看即可,代码没有经过压缩,可以很直观的看到各种方案的css适配写法
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。